Qpack refactoring, testing and bug fixes.
Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
This commit is contained in:
parent
a4938a3f4a
commit
191b1af880
|
@ -61,17 +61,52 @@ abstract class EncodableEntry
|
||||||
@Override
|
@Override
|
||||||
public void encode(ByteBuffer buffer, int base)
|
public void encode(ByteBuffer buffer, int base)
|
||||||
{
|
{
|
||||||
byte staticBit = _entry.isStatic() ? (byte)0x40 : (byte)0x00;
|
boolean isStatic = _entry.isStatic();
|
||||||
buffer.put((byte)(0x80 | staticBit));
|
if (isStatic)
|
||||||
int relativeIndex = _entry.getIndex() - base;
|
{
|
||||||
NBitInteger.encode(buffer, 6, relativeIndex);
|
// Indexed Field Line with Static Reference.
|
||||||
|
buffer.put((byte)(0x80 | 0x40));
|
||||||
|
int relativeIndex = _entry.getIndex();
|
||||||
|
NBitInteger.encode(buffer, 6, relativeIndex);
|
||||||
|
}
|
||||||
|
else if (_entry.getIndex() < base)
|
||||||
|
{
|
||||||
|
// Indexed Field Line with Dynamic Reference.
|
||||||
|
buffer.put((byte)0x80);
|
||||||
|
int relativeIndex = base - (_entry.getIndex() + 1);
|
||||||
|
NBitInteger.encode(buffer, 6, relativeIndex);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Indexed Field Line with Post-Base Index.
|
||||||
|
buffer.put((byte)0x10);
|
||||||
|
int relativeIndex = _entry.getIndex() - base;
|
||||||
|
NBitInteger.encode(buffer, 4, relativeIndex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getRequiredSize(int base)
|
public int getRequiredSize(int base)
|
||||||
{
|
{
|
||||||
int relativeIndex = _entry.getIndex() - base;
|
boolean isStatic = _entry.isStatic();
|
||||||
return 1 + NBitInteger.octectsNeeded(6, relativeIndex);
|
if (isStatic)
|
||||||
|
{
|
||||||
|
// Indexed Field Line with Static Reference.
|
||||||
|
int relativeIndex = _entry.getIndex();
|
||||||
|
return 1 + NBitInteger.octectsNeeded(6, relativeIndex);
|
||||||
|
}
|
||||||
|
else if (_entry.getIndex() < base)
|
||||||
|
{
|
||||||
|
// Indexed Field Line with Dynamic Reference.
|
||||||
|
int relativeIndex = base - (_entry.getIndex() + 1);
|
||||||
|
return 1 + NBitInteger.octectsNeeded(6, relativeIndex);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Indexed Field Line with Post-Base Index.
|
||||||
|
int relativeIndex = _entry.getIndex() - base;
|
||||||
|
return 1 + NBitInteger.octectsNeeded(4, relativeIndex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -97,13 +132,33 @@ abstract class EncodableEntry
|
||||||
@Override
|
@Override
|
||||||
public void encode(ByteBuffer buffer, int base)
|
public void encode(ByteBuffer buffer, int base)
|
||||||
{
|
{
|
||||||
byte allowIntermediary = 0x00; // TODO: this is 0x20 bit, when should this be set?
|
// TODO: when should this be set?
|
||||||
byte staticBit = _nameEntry.isStatic() ? (byte)0x10 : (byte)0x00;
|
// TODO: this is different for the Post-Base index case.
|
||||||
|
byte allowIntermediary = 0x00;
|
||||||
|
|
||||||
// Encode the prefix.
|
// Encode the prefix.
|
||||||
buffer.put((byte)(0x40 | allowIntermediary | staticBit));
|
boolean isStatic = _nameEntry.isStatic();
|
||||||
int relativeIndex = _nameEntry.getIndex() - base;
|
if (isStatic)
|
||||||
NBitInteger.encode(buffer, 4, relativeIndex);
|
{
|
||||||
|
// Literal Field Line with Static Name Reference.
|
||||||
|
buffer.put((byte)(0x40 | allowIntermediary | 0x10));
|
||||||
|
int relativeIndex = _nameEntry.getIndex();
|
||||||
|
NBitInteger.encode(buffer, 4, relativeIndex);
|
||||||
|
}
|
||||||
|
else if (_nameEntry.getIndex() < base)
|
||||||
|
{
|
||||||
|
// Literal Field Line with Dynamic Name Reference.
|
||||||
|
buffer.put((byte)(0x40 | allowIntermediary));
|
||||||
|
int relativeIndex = base - (_nameEntry.getIndex() + 1);
|
||||||
|
NBitInteger.encode(buffer, 4, relativeIndex);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Literal Field Line with Post-Base Name Reference.
|
||||||
|
buffer.put(allowIntermediary);
|
||||||
|
int relativeIndex = _nameEntry.getIndex() - base;
|
||||||
|
NBitInteger.encode(buffer, 3, relativeIndex);
|
||||||
|
}
|
||||||
|
|
||||||
// Encode the value.
|
// Encode the value.
|
||||||
String value = getValue();
|
String value = getValue();
|
||||||
|
|
|
@ -75,4 +75,17 @@ public class QpackContext
|
||||||
|
|
||||||
return _dynamicTable.get(index);
|
return _dynamicTable.get(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the relative Index of an entry.
|
||||||
|
* @param entry the entry to get the index of.
|
||||||
|
* @return the relative index of the entry.
|
||||||
|
*/
|
||||||
|
public int indexOf(Entry entry)
|
||||||
|
{
|
||||||
|
if (entry.isStatic())
|
||||||
|
return entry.getIndex();
|
||||||
|
|
||||||
|
return _dynamicTable.index(entry);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.http3.qpack;
|
package org.eclipse.jetty.http3.qpack;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -23,11 +24,13 @@ import org.eclipse.jetty.http.HttpHeader;
|
||||||
import org.eclipse.jetty.http3.qpack.generator.InsertCountIncrementInstruction;
|
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.SectionAcknowledgmentInstruction;
|
import org.eclipse.jetty.http3.qpack.generator.SectionAcknowledgmentInstruction;
|
||||||
|
import org.eclipse.jetty.http3.qpack.parser.DecoderInstructionParser;
|
||||||
import org.eclipse.jetty.http3.qpack.parser.EncodedFieldSection;
|
import org.eclipse.jetty.http3.qpack.parser.EncodedFieldSection;
|
||||||
import org.eclipse.jetty.http3.qpack.parser.NBitIntegerParser;
|
import org.eclipse.jetty.http3.qpack.parser.NBitIntegerParser;
|
||||||
import org.eclipse.jetty.http3.qpack.table.DynamicTable;
|
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.http3.qpack.table.StaticTable;
|
import org.eclipse.jetty.http3.qpack.table.StaticTable;
|
||||||
|
import org.eclipse.jetty.util.component.Dumpable;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@ -35,7 +38,7 @@ import org.slf4j.LoggerFactory;
|
||||||
* Qpack Decoder
|
* Qpack Decoder
|
||||||
* <p>This is not thread safe and may only be called by 1 thread at a time.</p>
|
* <p>This is not thread safe and may only be called by 1 thread at a time.</p>
|
||||||
*/
|
*/
|
||||||
public class QpackDecoder
|
public class QpackDecoder implements Dumpable
|
||||||
{
|
{
|
||||||
public static final Logger LOG = LoggerFactory.getLogger(QpackDecoder.class);
|
public static final Logger LOG = LoggerFactory.getLogger(QpackDecoder.class);
|
||||||
public static final HttpField.LongValueHttpField CONTENT_LENGTH_0 =
|
public static final HttpField.LongValueHttpField CONTENT_LENGTH_0 =
|
||||||
|
@ -44,6 +47,7 @@ public class QpackDecoder
|
||||||
private final Handler _handler;
|
private final Handler _handler;
|
||||||
private final QpackContext _context;
|
private final QpackContext _context;
|
||||||
private final MetaDataBuilder _builder;
|
private final MetaDataBuilder _builder;
|
||||||
|
private final DecoderInstructionParser _parser;
|
||||||
|
|
||||||
private final List<EncodedFieldSection> _encodedFieldSections = new ArrayList<>();
|
private final List<EncodedFieldSection> _encodedFieldSections = new ArrayList<>();
|
||||||
private final NBitIntegerParser _integerDecoder = new NBitIntegerParser();
|
private final NBitIntegerParser _integerDecoder = new NBitIntegerParser();
|
||||||
|
@ -56,6 +60,7 @@ public class QpackDecoder
|
||||||
_context = new QpackContext();
|
_context = new QpackContext();
|
||||||
_builder = new MetaDataBuilder(maxHeaderSize);
|
_builder = new MetaDataBuilder(maxHeaderSize);
|
||||||
_handler = handler;
|
_handler = handler;
|
||||||
|
_parser = new DecoderInstructionParser(new DecoderAdapter());
|
||||||
}
|
}
|
||||||
|
|
||||||
public QpackContext getQpackContext()
|
public QpackContext getQpackContext()
|
||||||
|
@ -67,7 +72,7 @@ public class QpackDecoder
|
||||||
{
|
{
|
||||||
void onHttpFields(int streamId, HttpFields httpFields);
|
void onHttpFields(int streamId, HttpFields httpFields);
|
||||||
|
|
||||||
void onInstruction(Instruction instruction);
|
void onInstruction(Instruction instruction) throws QpackException;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void decode(int streamId, ByteBuffer buffer) throws QpackException
|
public void decode(int streamId, ByteBuffer buffer) throws QpackException
|
||||||
|
@ -103,7 +108,7 @@ public class QpackDecoder
|
||||||
EncodedFieldSection encodedFieldSection = new EncodedFieldSection(streamId, requiredInsertCount, base);
|
EncodedFieldSection encodedFieldSection = new EncodedFieldSection(streamId, requiredInsertCount, base);
|
||||||
encodedFieldSection.parse(buffer);
|
encodedFieldSection.parse(buffer);
|
||||||
|
|
||||||
if (encodedFieldSection.getRequiredInsertCount() <= insertCount)
|
if (requiredInsertCount <= insertCount)
|
||||||
{
|
{
|
||||||
_handler.onHttpFields(streamId, encodedFieldSection.decode(_context));
|
_handler.onHttpFields(streamId, encodedFieldSection.decode(_context));
|
||||||
_handler.onInstruction(new SectionAcknowledgmentInstruction(streamId));
|
_handler.onInstruction(new SectionAcknowledgmentInstruction(streamId));
|
||||||
|
@ -115,6 +120,11 @@ public class QpackDecoder
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void parseInstruction(ByteBuffer buffer) throws QpackException
|
||||||
|
{
|
||||||
|
_parser.parse(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
private void checkEncodedFieldSections() throws QpackException
|
private void checkEncodedFieldSections() throws QpackException
|
||||||
{
|
{
|
||||||
int insertCount = _context.getDynamicTable().getInsertCount();
|
int insertCount = _context.getDynamicTable().getInsertCount();
|
||||||
|
@ -128,7 +138,7 @@ public class QpackDecoder
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCapacity(int capacity)
|
void setCapacity(int capacity)
|
||||||
{
|
{
|
||||||
synchronized (this)
|
synchronized (this)
|
||||||
{
|
{
|
||||||
|
@ -136,7 +146,7 @@ public class QpackDecoder
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void insert(int index) throws QpackException
|
void insert(int index) throws QpackException
|
||||||
{
|
{
|
||||||
synchronized (this)
|
synchronized (this)
|
||||||
{
|
{
|
||||||
|
@ -150,11 +160,11 @@ public class QpackDecoder
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void insert(int nameIndex, boolean isDynamicTableIndex, String value) throws QpackException
|
void insert(int nameIndex, boolean isDynamicTableIndex, String value) throws QpackException
|
||||||
{
|
{
|
||||||
synchronized (this)
|
synchronized (this)
|
||||||
{
|
{
|
||||||
StaticTable staticTable = _context.getStaticTable();
|
StaticTable staticTable = QpackContext.getStaticTable();
|
||||||
DynamicTable dynamicTable = _context.getDynamicTable();
|
DynamicTable dynamicTable = _context.getDynamicTable();
|
||||||
Entry referencedEntry = isDynamicTableIndex ? dynamicTable.get(nameIndex) : staticTable.get(nameIndex);
|
Entry referencedEntry = isDynamicTableIndex ? dynamicTable.get(nameIndex) : staticTable.get(nameIndex);
|
||||||
|
|
||||||
|
@ -166,14 +176,14 @@ public class QpackDecoder
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void insert(String name, String value) throws QpackException
|
void insert(String name, String value) throws QpackException
|
||||||
{
|
{
|
||||||
synchronized (this)
|
synchronized (this)
|
||||||
{
|
{
|
||||||
DynamicTable dynamicTable = _context.getDynamicTable();
|
|
||||||
Entry entry = new Entry(new HttpField(name, value));
|
Entry entry = new Entry(new HttpField(name, value));
|
||||||
|
|
||||||
// Add the new Entry to the DynamicTable.
|
// Add the new Entry to the DynamicTable.
|
||||||
|
DynamicTable dynamicTable = _context.getDynamicTable();
|
||||||
dynamicTable.add(entry);
|
dynamicTable.add(entry);
|
||||||
_handler.onInstruction(new InsertCountIncrementInstruction(1));
|
_handler.onInstruction(new InsertCountIncrementInstruction(1));
|
||||||
checkEncodedFieldSections();
|
checkEncodedFieldSections();
|
||||||
|
@ -210,9 +220,45 @@ public class QpackDecoder
|
||||||
return reqInsertCount;
|
return reqInsertCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dump(Appendable out, String indent) throws IOException
|
||||||
|
{
|
||||||
|
synchronized (this)
|
||||||
|
{
|
||||||
|
Dumpable.dumpObjects(out, indent, _context.getDynamicTable());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
return String.format("QpackDecoder@%x{%s}", hashCode(), _context);
|
return String.format("QpackDecoder@%x{%s}", hashCode(), _context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class DecoderAdapter implements DecoderInstructionParser.Handler
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onSetDynamicTableCapacity(int capacity)
|
||||||
|
{
|
||||||
|
setCapacity(capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDuplicate(int index) throws QpackException
|
||||||
|
{
|
||||||
|
insert(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInsertNameWithReference(int nameIndex, boolean isDynamicTableIndex, String value) throws QpackException
|
||||||
|
{
|
||||||
|
insert(nameIndex, isDynamicTableIndex, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInsertWithLiteralName(String name, String value) throws QpackException
|
||||||
|
{
|
||||||
|
insert(name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.http3.qpack;
|
package org.eclipse.jetty.http3.qpack;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.EnumMap;
|
import java.util.EnumMap;
|
||||||
|
@ -32,15 +33,17 @@ import org.eclipse.jetty.http3.qpack.generator.IndexedNameEntryInstruction;
|
||||||
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.LiteralNameEntryInstruction;
|
||||||
import org.eclipse.jetty.http3.qpack.generator.SetCapacityInstruction;
|
import org.eclipse.jetty.http3.qpack.generator.SetCapacityInstruction;
|
||||||
|
import org.eclipse.jetty.http3.qpack.parser.EncoderInstructionParser;
|
||||||
import org.eclipse.jetty.http3.qpack.table.DynamicTable;
|
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.io.ByteBufferPool;
|
import org.eclipse.jetty.io.ByteBufferPool;
|
||||||
import org.eclipse.jetty.io.NullByteBufferPool;
|
import org.eclipse.jetty.io.NullByteBufferPool;
|
||||||
import org.eclipse.jetty.util.BufferUtil;
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
|
import org.eclipse.jetty.util.component.Dumpable;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public class QpackEncoder
|
public class QpackEncoder implements Dumpable
|
||||||
{
|
{
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(QpackEncoder.class);
|
private static final Logger LOG = LoggerFactory.getLogger(QpackEncoder.class);
|
||||||
private static final HttpField[] STATUSES = new HttpField[599];
|
private static final HttpField[] STATUSES = new HttpField[599];
|
||||||
|
@ -96,17 +99,17 @@ public class QpackEncoder
|
||||||
|
|
||||||
public interface Handler
|
public interface Handler
|
||||||
{
|
{
|
||||||
void onInstruction(Instruction instruction);
|
void onInstruction(Instruction instruction) throws QpackException;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final ByteBufferPool _bufferPool;
|
private final ByteBufferPool _bufferPool;
|
||||||
private final Handler _handler;
|
private final Handler _handler;
|
||||||
private final QpackContext _context;
|
private final QpackContext _context;
|
||||||
private final int _maxBlockedStreams;
|
private final int _maxBlockedStreams;
|
||||||
private int _knownInsertCount;
|
|
||||||
|
|
||||||
private int _blockedStreams = 0;
|
|
||||||
private final Map<Integer, StreamInfo> _streamInfoMap = new HashMap<>();
|
private final Map<Integer, StreamInfo> _streamInfoMap = new HashMap<>();
|
||||||
|
private final EncoderInstructionParser _parser;
|
||||||
|
private int _knownInsertCount;
|
||||||
|
private int _blockedStreams = 0;
|
||||||
|
|
||||||
public QpackEncoder(Handler handler, int maxBlockedStreams)
|
public QpackEncoder(Handler handler, int maxBlockedStreams)
|
||||||
{
|
{
|
||||||
|
@ -120,6 +123,7 @@ public class QpackEncoder
|
||||||
_context = new QpackContext();
|
_context = new QpackContext();
|
||||||
_maxBlockedStreams = maxBlockedStreams;
|
_maxBlockedStreams = maxBlockedStreams;
|
||||||
_knownInsertCount = 0;
|
_knownInsertCount = 0;
|
||||||
|
_parser = new EncoderInstructionParser(new EncoderAdapter());
|
||||||
}
|
}
|
||||||
|
|
||||||
public QpackContext getQpackContext()
|
public QpackContext getQpackContext()
|
||||||
|
@ -127,7 +131,12 @@ public class QpackEncoder
|
||||||
return _context;
|
return _context;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCapacity(int capacity)
|
/**
|
||||||
|
* Set the capacity of the DynamicTable and send a instruction to set the capacity on the remote Decoder.
|
||||||
|
* @param capacity the new capacity.
|
||||||
|
* @throws QpackException
|
||||||
|
*/
|
||||||
|
public void setCapacity(int capacity) throws QpackException
|
||||||
{
|
{
|
||||||
synchronized (this)
|
synchronized (this)
|
||||||
{
|
{
|
||||||
|
@ -136,26 +145,23 @@ public class QpackEncoder
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void insertCountIncrement(int increment) throws QpackException
|
public void parseInstruction(ByteBuffer buffer) throws QpackException
|
||||||
|
{
|
||||||
|
_parser.parse(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void insertCountIncrement(int increment) throws QpackException
|
||||||
{
|
{
|
||||||
synchronized (this)
|
synchronized (this)
|
||||||
{
|
{
|
||||||
int insertCount = _context.getDynamicTable().getInsertCount();
|
int insertCount = _context.getDynamicTable().getInsertCount();
|
||||||
if (_knownInsertCount + increment > insertCount)
|
if (_knownInsertCount + increment > insertCount)
|
||||||
throw new QpackException.StreamException("KnownInsertCount incremented over InsertCount");
|
throw new QpackException.StreamException("KnownInsertCount incremented over InsertCount");
|
||||||
|
|
||||||
// TODO: release any references to entries which used to insert new entries.
|
|
||||||
for (Entry entry : _context.getDynamicTable())
|
|
||||||
{
|
|
||||||
if (entry.getIndex() > _knownInsertCount)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
_knownInsertCount += increment;
|
_knownInsertCount += increment;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sectionAcknowledgement(int streamId) throws QpackException
|
void sectionAcknowledgement(int streamId) throws QpackException
|
||||||
{
|
{
|
||||||
synchronized (this)
|
synchronized (this)
|
||||||
{
|
{
|
||||||
|
@ -165,6 +171,7 @@ public class QpackEncoder
|
||||||
|
|
||||||
// The KnownInsertCount should be updated to the earliest sent RequiredInsertCount on that stream.
|
// The KnownInsertCount should be updated to the earliest sent RequiredInsertCount on that stream.
|
||||||
StreamInfo.SectionInfo sectionInfo = streamInfo.acknowledge();
|
StreamInfo.SectionInfo sectionInfo = streamInfo.acknowledge();
|
||||||
|
sectionInfo.release();
|
||||||
_knownInsertCount = Math.max(_knownInsertCount, sectionInfo.getRequiredInsertCount());
|
_knownInsertCount = Math.max(_knownInsertCount, sectionInfo.getRequiredInsertCount());
|
||||||
|
|
||||||
// If we have no more outstanding section acknowledgments remove the StreamInfo.
|
// If we have no more outstanding section acknowledgments remove the StreamInfo.
|
||||||
|
@ -173,17 +180,23 @@ public class QpackEncoder
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void streamCancellation(int streamId) throws QpackException
|
void streamCancellation(int streamId) throws QpackException
|
||||||
{
|
{
|
||||||
synchronized (this)
|
synchronized (this)
|
||||||
{
|
{
|
||||||
StreamInfo streamInfo = _streamInfoMap.remove(streamId);
|
StreamInfo streamInfo = _streamInfoMap.remove(streamId);
|
||||||
if (streamInfo == null)
|
if (streamInfo == null)
|
||||||
throw new QpackException.StreamException("No StreamInfo for " + streamId);
|
throw new QpackException.StreamException("No StreamInfo for " + streamId);
|
||||||
|
|
||||||
|
// Release all referenced entries outstanding on the stream that was cancelled.
|
||||||
|
for (StreamInfo.SectionInfo sectionInfo : streamInfo)
|
||||||
|
{
|
||||||
|
sectionInfo.release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean referenceEntry(Entry entry)
|
private boolean referenceEntry(Entry entry, StreamInfo streamInfo)
|
||||||
{
|
{
|
||||||
if (entry == null)
|
if (entry == null)
|
||||||
return false;
|
return false;
|
||||||
|
@ -195,32 +208,28 @@ public class QpackEncoder
|
||||||
if (inEvictionZone)
|
if (inEvictionZone)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
StreamInfo.SectionInfo sectionInfo = streamInfo.getCurrentSectionInfo();
|
||||||
|
|
||||||
// If they have already acknowledged this entry we can reference it straight away.
|
// If they have already acknowledged this entry we can reference it straight away.
|
||||||
if (_knownInsertCount >= entry.getIndex() + 1)
|
if (_knownInsertCount >= entry.getIndex() + 1)
|
||||||
{
|
{
|
||||||
entry.reference();
|
sectionInfo.reference(entry);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean referenceEntry(Entry entry, StreamInfo streamInfo)
|
|
||||||
{
|
|
||||||
if (referenceEntry(entry))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
// We may need to risk blocking the stream in order to reference it.
|
// We may need to risk blocking the stream in order to reference it.
|
||||||
if (streamInfo.isBlocked())
|
if (streamInfo.isBlocked())
|
||||||
{
|
{
|
||||||
streamInfo.getCurrentSectionInfo().block();
|
sectionInfo.block();
|
||||||
|
sectionInfo.reference(entry);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_blockedStreams < _maxBlockedStreams)
|
if (_blockedStreams < _maxBlockedStreams)
|
||||||
{
|
{
|
||||||
_blockedStreams++;
|
_blockedStreams++;
|
||||||
streamInfo.getCurrentSectionInfo().block();
|
sectionInfo.block();
|
||||||
|
sectionInfo.reference(entry);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -259,7 +268,6 @@ public class QpackEncoder
|
||||||
synchronized (this)
|
synchronized (this)
|
||||||
{
|
{
|
||||||
DynamicTable dynamicTable = _context.getDynamicTable();
|
DynamicTable dynamicTable = _context.getDynamicTable();
|
||||||
dynamicTable.evict();
|
|
||||||
|
|
||||||
StreamInfo streamInfo = _streamInfoMap.get(streamId);
|
StreamInfo streamInfo = _streamInfoMap.get(streamId);
|
||||||
if (streamInfo == null)
|
if (streamInfo == null)
|
||||||
|
@ -285,6 +293,7 @@ public class QpackEncoder
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sectionInfo.setRequiredInsertCount(requiredInsertCount);
|
||||||
base = dynamicTable.getBase();
|
base = dynamicTable.getBase();
|
||||||
encodedInsertCount = encodeInsertCount(requiredInsertCount, dynamicTable.getCapacity());
|
encodedInsertCount = encodeInsertCount(requiredInsertCount, dynamicTable.getCapacity());
|
||||||
signBit = base < requiredInsertCount;
|
signBit = base < requiredInsertCount;
|
||||||
|
@ -310,7 +319,7 @@ public class QpackEncoder
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
private EncodableEntry encode(StreamInfo streamInfo, HttpField field)
|
private EncodableEntry encode(StreamInfo streamInfo, HttpField field) throws QpackException
|
||||||
{
|
{
|
||||||
DynamicTable dynamicTable = _context.getDynamicTable();
|
DynamicTable dynamicTable = _context.getDynamicTable();
|
||||||
|
|
||||||
|
@ -322,21 +331,22 @@ public class QpackEncoder
|
||||||
if (field instanceof PreEncodedHttpField)
|
if (field instanceof PreEncodedHttpField)
|
||||||
return EncodableEntry.getPreEncodedEntry((PreEncodedHttpField)field);
|
return EncodableEntry.getPreEncodedEntry((PreEncodedHttpField)field);
|
||||||
|
|
||||||
boolean canCreateEntry = shouldIndex(field) && (Entry.getSize(field) <= dynamicTable.getSpace());
|
boolean canCreateEntry = shouldIndex(field) && dynamicTable.canInsert(field);
|
||||||
|
|
||||||
Entry entry = _context.get(field);
|
Entry entry = _context.get(field);
|
||||||
if (entry != null && referenceEntry(entry, streamInfo))
|
if (referenceEntry(entry, streamInfo))
|
||||||
{
|
{
|
||||||
return EncodableEntry.getReferencedEntry(entry);
|
return EncodableEntry.getReferencedEntry(entry);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Should we duplicate this entry.
|
// Should we duplicate this entry.
|
||||||
if (entry != null && dynamicTable.canReference(entry) && canCreateEntry)
|
if (entry != null && canCreateEntry)
|
||||||
{
|
{
|
||||||
|
int index = _context.indexOf(entry);
|
||||||
Entry newEntry = new Entry(field);
|
Entry newEntry = new Entry(field);
|
||||||
dynamicTable.add(newEntry);
|
dynamicTable.add(newEntry);
|
||||||
_handler.onInstruction(new DuplicateInstruction(entry.getIndex()));
|
_handler.onInstruction(new DuplicateInstruction(index));
|
||||||
|
|
||||||
// Should we reference this entry and risk blocking.
|
// Should we reference this entry and risk blocking.
|
||||||
if (referenceEntry(newEntry, streamInfo))
|
if (referenceEntry(newEntry, streamInfo))
|
||||||
|
@ -346,14 +356,15 @@ public class QpackEncoder
|
||||||
|
|
||||||
boolean huffman = shouldHuffmanEncode(field);
|
boolean huffman = shouldHuffmanEncode(field);
|
||||||
Entry nameEntry = _context.get(field.getName());
|
Entry nameEntry = _context.get(field.getName());
|
||||||
if (nameEntry != null && referenceEntry(nameEntry, streamInfo))
|
if (referenceEntry(nameEntry, streamInfo))
|
||||||
{
|
{
|
||||||
// Should we copy this entry
|
// Should we copy this entry
|
||||||
if (canCreateEntry)
|
if (canCreateEntry)
|
||||||
{
|
{
|
||||||
|
int index = _context.indexOf(nameEntry);
|
||||||
Entry newEntry = new Entry(field);
|
Entry newEntry = new Entry(field);
|
||||||
dynamicTable.add(newEntry);
|
dynamicTable.add(newEntry);
|
||||||
_handler.onInstruction(new IndexedNameEntryInstruction(!nameEntry.isStatic(), nameEntry.getIndex(), huffman, field.getValue()));
|
_handler.onInstruction(new IndexedNameEntryInstruction(!nameEntry.isStatic(), index, huffman, field.getValue()));
|
||||||
|
|
||||||
// Should we reference this entry and risk blocking.
|
// Should we reference this entry and risk blocking.
|
||||||
if (referenceEntry(newEntry, streamInfo))
|
if (referenceEntry(newEntry, streamInfo))
|
||||||
|
@ -379,38 +390,40 @@ public class QpackEncoder
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean insert(HttpField field)
|
public boolean insert(HttpField field) throws QpackException
|
||||||
{
|
{
|
||||||
synchronized (this)
|
synchronized (this)
|
||||||
{
|
{
|
||||||
DynamicTable dynamicTable = _context.getDynamicTable();
|
DynamicTable dynamicTable = _context.getDynamicTable();
|
||||||
dynamicTable.evict();
|
|
||||||
|
|
||||||
if (field.getValue() == null)
|
if (field.getValue() == null)
|
||||||
field = new HttpField(field.getHeader(), field.getName(), "");
|
field = new HttpField(field.getHeader(), field.getName(), "");
|
||||||
|
|
||||||
boolean canCreateEntry = shouldIndex(field) && (Entry.getSize(field) <= dynamicTable.getSpace());
|
boolean canCreateEntry = shouldIndex(field) && dynamicTable.canInsert(field);
|
||||||
if (!canCreateEntry)
|
if (!canCreateEntry)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
Entry newEntry = new Entry(field);
|
// We can always reference on insertion as it will always arrive before any eviction.
|
||||||
dynamicTable.add(newEntry);
|
|
||||||
|
|
||||||
Entry entry = _context.get(field);
|
Entry entry = _context.get(field);
|
||||||
if (referenceEntry(entry))
|
if (entry != null)
|
||||||
{
|
{
|
||||||
_handler.onInstruction(new DuplicateInstruction(entry.getIndex()));
|
int index = _context.indexOf(entry);
|
||||||
|
dynamicTable.add(new Entry(field));
|
||||||
|
_handler.onInstruction(new DuplicateInstruction(index));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean huffman = shouldHuffmanEncode(field);
|
boolean huffman = shouldHuffmanEncode(field);
|
||||||
Entry nameEntry = _context.get(field.getName());
|
Entry nameEntry = _context.get(field.getName());
|
||||||
if (referenceEntry(nameEntry))
|
if (nameEntry != null)
|
||||||
{
|
{
|
||||||
_handler.onInstruction(new IndexedNameEntryInstruction(!nameEntry.isStatic(), nameEntry.getIndex(), huffman, field.getValue()));
|
int index = _context.indexOf(nameEntry);
|
||||||
|
dynamicTable.add(new Entry(field));
|
||||||
|
_handler.onInstruction(new IndexedNameEntryInstruction(!nameEntry.isStatic(), index, huffman, field.getValue()));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dynamicTable.add(new Entry(field));
|
||||||
_handler.onInstruction(new LiteralNameEntryInstruction(huffman, field.getName(), huffman, field.getValue()));
|
_handler.onInstruction(new LiteralNameEntryInstruction(huffman, field.getName(), huffman, field.getValue()));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -424,4 +437,34 @@ public class QpackEncoder
|
||||||
int maxEntries = maxTableCapacity / 32;
|
int maxEntries = maxTableCapacity / 32;
|
||||||
return (reqInsertCount % (2 * maxEntries)) + 1;
|
return (reqInsertCount % (2 * maxEntries)) + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class EncoderAdapter implements EncoderInstructionParser.Handler
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onSectionAcknowledgement(int streamId) throws QpackException
|
||||||
|
{
|
||||||
|
sectionAcknowledgement(streamId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStreamCancellation(int streamId) throws QpackException
|
||||||
|
{
|
||||||
|
streamCancellation(streamId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInsertCountIncrement(int increment) throws QpackException
|
||||||
|
{
|
||||||
|
insertCountIncrement(increment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dump(Appendable out, String indent) throws IOException
|
||||||
|
{
|
||||||
|
synchronized (this)
|
||||||
|
{
|
||||||
|
Dumpable.dumpObjects(out, indent, _context.getDynamicTable());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,10 +13,15 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.http3.qpack;
|
package org.eclipse.jetty.http3.qpack;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
|
|
||||||
class StreamInfo
|
import org.eclipse.jetty.http3.qpack.table.Entry;
|
||||||
|
|
||||||
|
class StreamInfo implements Iterable<StreamInfo.SectionInfo>
|
||||||
{
|
{
|
||||||
private final int _streamId;
|
private final int _streamId;
|
||||||
private final Queue<SectionInfo> _sectionInfos = new LinkedList<>();
|
private final Queue<SectionInfo> _sectionInfos = new LinkedList<>();
|
||||||
|
@ -62,8 +67,15 @@ class StreamInfo
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<SectionInfo> iterator()
|
||||||
|
{
|
||||||
|
return _sectionInfos.iterator();
|
||||||
|
}
|
||||||
|
|
||||||
public static class SectionInfo
|
public static class SectionInfo
|
||||||
{
|
{
|
||||||
|
private final List<Entry> _entries = new ArrayList<>();
|
||||||
private int _requiredInsertCount;
|
private int _requiredInsertCount;
|
||||||
private boolean _block = false;
|
private boolean _block = false;
|
||||||
|
|
||||||
|
@ -77,6 +89,21 @@ class StreamInfo
|
||||||
return _block;
|
return _block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void reference(Entry entry)
|
||||||
|
{
|
||||||
|
entry.reference();
|
||||||
|
_entries.add(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void release()
|
||||||
|
{
|
||||||
|
for (Entry entry : _entries)
|
||||||
|
{
|
||||||
|
entry.release();
|
||||||
|
}
|
||||||
|
_entries.clear();
|
||||||
|
}
|
||||||
|
|
||||||
public void setRequiredInsertCount(int requiredInsertCount)
|
public void setRequiredInsertCount(int requiredInsertCount)
|
||||||
{
|
{
|
||||||
_requiredInsertCount = requiredInsertCount;
|
_requiredInsertCount = requiredInsertCount;
|
||||||
|
|
|
@ -15,7 +15,6 @@ package org.eclipse.jetty.http3.qpack.parser;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
import org.eclipse.jetty.http3.qpack.QpackDecoder;
|
|
||||||
import org.eclipse.jetty.http3.qpack.QpackException;
|
import org.eclipse.jetty.http3.qpack.QpackException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -61,45 +60,6 @@ public class DecoderInstructionParser
|
||||||
void onInsertWithLiteralName(String name, String value) throws QpackException;
|
void onInsertWithLiteralName(String name, String value) throws QpackException;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class DecoderAdapter implements Handler
|
|
||||||
{
|
|
||||||
private final QpackDecoder _decoder;
|
|
||||||
|
|
||||||
public DecoderAdapter(QpackDecoder decoder)
|
|
||||||
{
|
|
||||||
_decoder = decoder;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSetDynamicTableCapacity(int capacity)
|
|
||||||
{
|
|
||||||
_decoder.setCapacity(capacity);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDuplicate(int index) throws QpackException
|
|
||||||
{
|
|
||||||
_decoder.insert(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onInsertNameWithReference(int nameIndex, boolean isDynamicTableIndex, String value) throws QpackException
|
|
||||||
{
|
|
||||||
_decoder.insert(nameIndex, isDynamicTableIndex, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onInsertWithLiteralName(String name, String value) throws QpackException
|
|
||||||
{
|
|
||||||
_decoder.insert(name, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public DecoderInstructionParser(QpackDecoder decoder)
|
|
||||||
{
|
|
||||||
this(new DecoderAdapter(decoder));
|
|
||||||
}
|
|
||||||
|
|
||||||
public DecoderInstructionParser(Handler handler)
|
public DecoderInstructionParser(Handler handler)
|
||||||
{
|
{
|
||||||
_handler = handler;
|
_handler = handler;
|
||||||
|
|
|
@ -118,7 +118,7 @@ public class EncodedFieldSection
|
||||||
byte firstByte = buffer.get(buffer.position());
|
byte firstByte = buffer.get(buffer.position());
|
||||||
boolean allowEncoding = (firstByte & 0x10) != 0;
|
boolean allowEncoding = (firstByte & 0x10) != 0;
|
||||||
|
|
||||||
_stringParser.setPrefix(3);
|
_stringParser.setPrefix(4);
|
||||||
String name = _stringParser.decode(buffer);
|
String name = _stringParser.decode(buffer);
|
||||||
if (name == null)
|
if (name == null)
|
||||||
throw new QpackException.CompressionException("Invalid Name");
|
throw new QpackException.CompressionException("Invalid Name");
|
||||||
|
@ -136,7 +136,7 @@ public class EncodedFieldSection
|
||||||
byte firstByte = buffer.get(buffer.position());
|
byte firstByte = buffer.get(buffer.position());
|
||||||
boolean allowEncoding = (firstByte & 0x08) != 0;
|
boolean allowEncoding = (firstByte & 0x08) != 0;
|
||||||
|
|
||||||
_integerParser.setPrefix(3);
|
_integerParser.setPrefix(4);
|
||||||
int nameIndex = _integerParser.decode(buffer);
|
int nameIndex = _integerParser.decode(buffer);
|
||||||
if (nameIndex < 0)
|
if (nameIndex < 0)
|
||||||
throw new QpackException.CompressionException("Invalid Index");
|
throw new QpackException.CompressionException("Invalid Index");
|
||||||
|
@ -199,9 +199,9 @@ public class EncodedFieldSection
|
||||||
public HttpField decode(QpackContext context) throws QpackException
|
public HttpField decode(QpackContext context) throws QpackException
|
||||||
{
|
{
|
||||||
if (_dynamicTable)
|
if (_dynamicTable)
|
||||||
return context.getDynamicTable().getAbsolute(_base + _index).getHttpField();
|
return context.getDynamicTable().getAbsolute(_base - (_index + 1)).getHttpField();
|
||||||
else
|
else
|
||||||
return context.getStaticTable().get(_index).getHttpField();
|
return QpackContext.getStaticTable().get(_index).getHttpField();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,7 +217,7 @@ public class EncodedFieldSection
|
||||||
@Override
|
@Override
|
||||||
public HttpField decode(QpackContext context) throws QpackException
|
public HttpField decode(QpackContext context) throws QpackException
|
||||||
{
|
{
|
||||||
return context.getDynamicTable().getAbsolute(_base - _index).getHttpField();
|
return context.getDynamicTable().getAbsolute(_base + _index).getHttpField();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -242,7 +242,7 @@ public class EncodedFieldSection
|
||||||
{
|
{
|
||||||
HttpField field;
|
HttpField field;
|
||||||
if (_dynamicTable)
|
if (_dynamicTable)
|
||||||
field = context.getDynamicTable().getAbsolute(_base + _nameIndex + 1).getHttpField();
|
field = context.getDynamicTable().getAbsolute(_base - (_nameIndex + 1)).getHttpField();
|
||||||
else
|
else
|
||||||
field = QpackContext.getStaticTable().get(_nameIndex).getHttpField();
|
field = QpackContext.getStaticTable().get(_nameIndex).getHttpField();
|
||||||
|
|
||||||
|
@ -266,7 +266,7 @@ public class EncodedFieldSection
|
||||||
@Override
|
@Override
|
||||||
public HttpField decode(QpackContext context) throws QpackException
|
public HttpField decode(QpackContext context) throws QpackException
|
||||||
{
|
{
|
||||||
HttpField field = context.getDynamicTable().getAbsolute(_base - _nameIndex).getHttpField();
|
HttpField field = context.getDynamicTable().getAbsolute(_base + _nameIndex).getHttpField();
|
||||||
return new HttpField(field.getHeader(), field.getName(), _value);
|
return new HttpField(field.getHeader(), field.getName(), _value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,6 @@ package org.eclipse.jetty.http3.qpack.parser;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
import org.eclipse.jetty.http3.qpack.QpackEncoder;
|
|
||||||
import org.eclipse.jetty.http3.qpack.QpackException;
|
import org.eclipse.jetty.http3.qpack.QpackException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -48,39 +47,6 @@ public class EncoderInstructionParser
|
||||||
void onInsertCountIncrement(int increment) throws QpackException;
|
void onInsertCountIncrement(int increment) throws QpackException;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class EncoderAdapter implements Handler
|
|
||||||
{
|
|
||||||
private final QpackEncoder _encoder;
|
|
||||||
|
|
||||||
public EncoderAdapter(QpackEncoder encoder)
|
|
||||||
{
|
|
||||||
_encoder = encoder;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSectionAcknowledgement(int streamId) throws QpackException
|
|
||||||
{
|
|
||||||
_encoder.sectionAcknowledgement(streamId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStreamCancellation(int streamId) throws QpackException
|
|
||||||
{
|
|
||||||
_encoder.streamCancellation(streamId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onInsertCountIncrement(int increment) throws QpackException
|
|
||||||
{
|
|
||||||
_encoder.insertCountIncrement(increment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public EncoderInstructionParser(QpackEncoder encoder)
|
|
||||||
{
|
|
||||||
this(new EncoderAdapter(encoder));
|
|
||||||
}
|
|
||||||
|
|
||||||
public EncoderInstructionParser(Handler handler)
|
public EncoderInstructionParser(Handler handler)
|
||||||
{
|
{
|
||||||
_handler = handler;
|
_handler = handler;
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.http3.qpack.table;
|
package org.eclipse.jetty.http3.qpack.table;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
@ -20,10 +21,10 @@ 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.QpackException;
|
import org.eclipse.jetty.http3.qpack.QpackException;
|
||||||
|
import org.eclipse.jetty.util.component.Dumpable;
|
||||||
|
|
||||||
public class DynamicTable implements Iterable<Entry>
|
public class DynamicTable implements Iterable<Entry>, Dumpable
|
||||||
{
|
{
|
||||||
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<>();
|
||||||
|
@ -34,9 +35,38 @@ public class DynamicTable implements Iterable<Entry>
|
||||||
private int _absoluteIndex;
|
private int _absoluteIndex;
|
||||||
private int _drainingIndex;
|
private int _drainingIndex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an entry into the Dynamic Table. This will throw if it is not possible to insert this entry.
|
||||||
|
* Use {@link #canInsert(HttpField)} to check whether it is possible to insert this entry.
|
||||||
|
* @param entry the entry to insert.
|
||||||
|
*/
|
||||||
public void add(Entry entry)
|
public void add(Entry entry)
|
||||||
{
|
{
|
||||||
|
// Evict entries until there is space for the new entry.
|
||||||
|
Iterator<Entry> iterator = _entries.iterator();
|
||||||
int entrySize = entry.getSize();
|
int entrySize = entry.getSize();
|
||||||
|
while (getSpace() < entrySize)
|
||||||
|
{
|
||||||
|
if (!iterator.hasNext())
|
||||||
|
throw new IllegalStateException("not enough space in dynamic table to add entry");
|
||||||
|
|
||||||
|
Entry e = iterator.next();
|
||||||
|
if (e.getReferenceCount() != 0)
|
||||||
|
throw new IllegalStateException("cannot evict entry that is still referenced");
|
||||||
|
|
||||||
|
// Evict the entry from the DynamicTable.
|
||||||
|
_size -= e.getSize();
|
||||||
|
iterator.remove();
|
||||||
|
|
||||||
|
HttpField httpField = e.getHttpField();
|
||||||
|
if (e == _fieldMap.get(httpField))
|
||||||
|
_fieldMap.remove(httpField);
|
||||||
|
|
||||||
|
String name = httpField.getLowerCaseName();
|
||||||
|
if (e == _nameMap.get(name))
|
||||||
|
_nameMap.remove(name);
|
||||||
|
}
|
||||||
|
|
||||||
if (entrySize + _size > _capacity)
|
if (entrySize + _size > _capacity)
|
||||||
throw new IllegalStateException("No available space");
|
throw new IllegalStateException("No available space");
|
||||||
_size += entrySize;
|
_size += entrySize;
|
||||||
|
@ -51,16 +81,73 @@ public class DynamicTable implements Iterable<Entry>
|
||||||
_drainingIndex = getDrainingIndex();
|
_drainingIndex = getDrainingIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int index(Entry entry)
|
/**
|
||||||
|
* Is there enough room to insert a new entry into the Dynamic Table, possibly evicting unreferenced entries.
|
||||||
|
* @param field the HttpField to insert into the table.
|
||||||
|
* @return if an entry with this HttpField can be inserted.
|
||||||
|
*/
|
||||||
|
public boolean canInsert(HttpField field)
|
||||||
{
|
{
|
||||||
return _entries.indexOf(entry);
|
int availableSpace = getSpace();
|
||||||
|
int requiredSpace = Entry.getSize(field);
|
||||||
|
|
||||||
|
if (availableSpace >= requiredSpace)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (requiredSpace > _capacity)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Could we potentially evict enough space to insert this new field.
|
||||||
|
for (Entry entry : _entries)
|
||||||
|
{
|
||||||
|
if (entry.getReferenceCount() != 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
availableSpace += entry.getSize();
|
||||||
|
if (availableSpace >= requiredSpace)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the relative index of an entry in the Dynamic Table.
|
||||||
|
* @param entry the entry to find the relative index of.
|
||||||
|
* @return the relative index of this entry.
|
||||||
|
*/
|
||||||
|
public int index(Entry entry)
|
||||||
|
{
|
||||||
|
if (_entries.isEmpty())
|
||||||
|
throw new IllegalArgumentException("Invalid Index");
|
||||||
|
|
||||||
|
Entry firstEntry = _entries.get(0);
|
||||||
|
int index = entry.getIndex() - firstEntry.getIndex();
|
||||||
|
if (index >= _entries.size())
|
||||||
|
throw new IllegalArgumentException("Invalid Index");
|
||||||
|
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an entry from the Dynamic table given an absolute index.
|
||||||
|
* @param absoluteIndex the absolute index of the entry in the table.
|
||||||
|
* @return the entry with the absolute index.
|
||||||
|
*/
|
||||||
public Entry getAbsolute(int absoluteIndex) throws QpackException
|
public Entry getAbsolute(int absoluteIndex) throws QpackException
|
||||||
{
|
{
|
||||||
if (absoluteIndex < 0)
|
if (absoluteIndex < 0)
|
||||||
throw new QpackException.CompressionException("Invalid Index");
|
throw new QpackException.CompressionException("Invalid Index");
|
||||||
return _entries.stream().filter(e -> e.getIndex() == absoluteIndex).findFirst().orElse(null);
|
|
||||||
|
if (_entries.isEmpty())
|
||||||
|
throw new IllegalArgumentException("Invalid Index");
|
||||||
|
|
||||||
|
Entry firstEntry = _entries.get(0);
|
||||||
|
int index = absoluteIndex - firstEntry.getIndex();
|
||||||
|
if (index >= _entries.size())
|
||||||
|
throw new IllegalArgumentException("Invalid Index");
|
||||||
|
|
||||||
|
return _entries.get(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Entry get(int index)
|
public Entry get(int index)
|
||||||
|
@ -112,31 +199,21 @@ public class DynamicTable implements Iterable<Entry>
|
||||||
|
|
||||||
public void setCapacity(int capacity)
|
public void setCapacity(int capacity)
|
||||||
{
|
{
|
||||||
if (QpackContext.LOG.isDebugEnabled())
|
|
||||||
QpackContext.LOG.debug(String.format("HdrTbl[%x] resized max=%d->%d", hashCode(), _capacity, capacity));
|
|
||||||
_capacity = capacity;
|
_capacity = capacity;
|
||||||
}
|
|
||||||
|
|
||||||
public boolean canReference(Entry entry)
|
Iterator<Entry> iterator = _entries.iterator();
|
||||||
{
|
while (_size > _capacity)
|
||||||
return entry.getIndex() >= _drainingIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void evict()
|
|
||||||
{
|
|
||||||
for (Entry entry : _entries)
|
|
||||||
{
|
{
|
||||||
if (entry.getIndex() >= _drainingIndex)
|
if (!iterator.hasNext())
|
||||||
return;
|
throw new IllegalStateException();
|
||||||
|
|
||||||
// We can only evict if there are no references outstanding to this entry.
|
Entry entry = iterator.next();
|
||||||
if (entry.getReferenceCount() != 0)
|
if (entry.getReferenceCount() != 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Evict the entry from the DynamicTable.
|
// Evict the entry from the DynamicTable.
|
||||||
_size -= entry.getSize();
|
_size -= entry.getSize();
|
||||||
if (entry != _entries.remove(0))
|
iterator.remove();
|
||||||
throw new IllegalStateException("Corruption in DynamicTable");
|
|
||||||
|
|
||||||
HttpField httpField = entry.getHttpField();
|
HttpField httpField = entry.getHttpField();
|
||||||
if (entry == _fieldMap.get(httpField))
|
if (entry == _fieldMap.get(httpField))
|
||||||
|
@ -148,6 +225,11 @@ public class DynamicTable implements Iterable<Entry>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean canReference(Entry entry)
|
||||||
|
{
|
||||||
|
return entry.getIndex() >= _drainingIndex;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Entries with indexes lower than the draining index should not be referenced.
|
* Entries with indexes lower than the draining index should not be referenced.
|
||||||
* This allows these entries to eventually be evicted as no more references will be made to them.
|
* This allows these entries to eventually be evicted as no more references will be made to them.
|
||||||
|
@ -183,6 +265,12 @@ public class DynamicTable implements Iterable<Entry>
|
||||||
return _entries.iterator();
|
return _entries.iterator();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dump(Appendable out, String indent) throws IOException
|
||||||
|
{
|
||||||
|
Dumpable.dumpObjects(out, indent, _entries);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
|
|
|
@ -65,6 +65,11 @@ public class Entry
|
||||||
_referenceCount.incrementAndGet();
|
_referenceCount.incrementAndGet();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void release()
|
||||||
|
{
|
||||||
|
_referenceCount.decrementAndGet();
|
||||||
|
}
|
||||||
|
|
||||||
public int getReferenceCount()
|
public int getReferenceCount()
|
||||||
{
|
{
|
||||||
return _referenceCount.get();
|
return _referenceCount.get();
|
||||||
|
@ -83,7 +88,13 @@ public class Entry
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
return String.format("{%s,%d,%s,%x}", isStatic() ? "S" : "D", _absoluteIndex, _field, hashCode());
|
return String.format("%s@%x{index=%d, refs=%d, field=\"%s\"}", getClass().getSimpleName(), hashCode(),
|
||||||
|
_absoluteIndex, _referenceCount.get(), _field);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getSize(Entry entry)
|
||||||
|
{
|
||||||
|
return getSize(entry.getHttpField());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getSize(HttpField field)
|
public static int getSize(HttpField field)
|
||||||
|
|
|
@ -25,11 +25,7 @@ import org.eclipse.jetty.http3.qpack.generator.SectionAcknowledgmentInstruction;
|
||||||
import org.eclipse.jetty.http3.qpack.generator.SetCapacityInstruction;
|
import org.eclipse.jetty.http3.qpack.generator.SetCapacityInstruction;
|
||||||
import org.eclipse.jetty.http3.qpack.parser.DecoderInstructionParser;
|
import org.eclipse.jetty.http3.qpack.parser.DecoderInstructionParser;
|
||||||
import org.eclipse.jetty.http3.qpack.parser.EncoderInstructionParser;
|
import org.eclipse.jetty.http3.qpack.parser.EncoderInstructionParser;
|
||||||
import org.eclipse.jetty.io.ByteBufferPool;
|
|
||||||
import org.eclipse.jetty.io.NullByteBufferPool;
|
|
||||||
import org.eclipse.jetty.util.BufferUtil;
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
import org.eclipse.jetty.util.TypeUtil;
|
|
||||||
import org.hamcrest.Matcher;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
@ -66,8 +62,6 @@ public class EncodeDecodeTest
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
_decoder = new QpackDecoder(_decoderHandler, MAX_HEADER_SIZE);
|
_decoder = new QpackDecoder(_decoderHandler, MAX_HEADER_SIZE);
|
||||||
_encoderInstructionParser = new EncoderInstructionParser(_encoder);
|
|
||||||
_decoderInstructionParser = new DecoderInstructionParser(_decoder);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -79,7 +73,7 @@ public class EncodeDecodeTest
|
||||||
|
|
||||||
ByteBuffer buffer = _encoder.encode(streamId, httpFields);
|
ByteBuffer buffer = _encoder.encode(streamId, httpFields);
|
||||||
assertNull(_encoderHandler.getInstruction());
|
assertNull(_encoderHandler.getInstruction());
|
||||||
assertThat(BufferUtil.toHexString(buffer), equalsHex("0000 510b 2f69 6e64 6578 2e68 746d 6c"));
|
assertThat(BufferUtil.toHexString(buffer), QpackTestUtil.equalsHex("0000 510b 2f69 6e64 6578 2e68 746d 6c"));
|
||||||
assertTrue(_encoderHandler.isEmpty());
|
assertTrue(_encoderHandler.isEmpty());
|
||||||
|
|
||||||
_decoder.decode(streamId, buffer);
|
_decoder.decode(streamId, buffer);
|
||||||
|
@ -88,7 +82,7 @@ public class EncodeDecodeTest
|
||||||
assertThat(_decoderHandler.getInstruction(), instanceOf(SectionAcknowledgmentInstruction.class));
|
assertThat(_decoderHandler.getInstruction(), instanceOf(SectionAcknowledgmentInstruction.class));
|
||||||
assertTrue(_decoderHandler.isEmpty());
|
assertTrue(_decoderHandler.isEmpty());
|
||||||
|
|
||||||
_encoderInstructionParser.parse(toBuffer(new SectionAcknowledgmentInstruction(streamId)));
|
_encoderInstructionParser.parse(QpackTestUtil.toBuffer(new SectionAcknowledgmentInstruction(streamId)));
|
||||||
|
|
||||||
// B.2. Dynamic Table.
|
// B.2. Dynamic Table.
|
||||||
|
|
||||||
|
@ -97,9 +91,9 @@ public class EncodeDecodeTest
|
||||||
Instruction instruction = _encoderHandler.getInstruction();
|
Instruction instruction = _encoderHandler.getInstruction();
|
||||||
assertThat(instruction, instanceOf(SetCapacityInstruction.class));
|
assertThat(instruction, instanceOf(SetCapacityInstruction.class));
|
||||||
assertThat(((SetCapacityInstruction)instruction).getCapacity(), is(220));
|
assertThat(((SetCapacityInstruction)instruction).getCapacity(), is(220));
|
||||||
assertThat(toString(instruction), equalsHex("3fbd01"));
|
assertThat(QpackTestUtil.toHexString(instruction), QpackTestUtil.equalsHex("3fbd01"));
|
||||||
|
|
||||||
_decoderInstructionParser.parse(toHex("3fbd01"));
|
_decoderInstructionParser.parse(QpackTestUtil.hexToBuffer("3fbd01"));
|
||||||
assertThat(_decoder.getQpackContext().getDynamicTable().getCapacity(), is(220));
|
assertThat(_decoder.getQpackContext().getDynamicTable().getCapacity(), is(220));
|
||||||
|
|
||||||
// Insert with named referenced to static table. Test we get two instructions generated to add to the dynamic table.
|
// Insert with named referenced to static table. Test we get two instructions generated to add to the dynamic table.
|
||||||
|
@ -113,24 +107,24 @@ public class EncodeDecodeTest
|
||||||
assertThat(instruction, instanceOf(IndexedNameEntryInstruction.class));
|
assertThat(instruction, instanceOf(IndexedNameEntryInstruction.class));
|
||||||
assertThat(((IndexedNameEntryInstruction)instruction).getIndex(), is(0));
|
assertThat(((IndexedNameEntryInstruction)instruction).getIndex(), is(0));
|
||||||
assertThat(((IndexedNameEntryInstruction)instruction).getValue(), is("www.example.com"));
|
assertThat(((IndexedNameEntryInstruction)instruction).getValue(), is("www.example.com"));
|
||||||
assertThat(toString(instruction), equalsHex("c00f 7777 772e 6578 616d 706c 652e 636f 6d"));
|
assertThat(QpackTestUtil.toHexString(instruction), QpackTestUtil.equalsHex("c00f 7777 772e 6578 616d 706c 652e 636f 6d"));
|
||||||
|
|
||||||
instruction = _encoderHandler.getInstruction();
|
instruction = _encoderHandler.getInstruction();
|
||||||
assertThat(instruction, instanceOf(IndexedNameEntryInstruction.class));
|
assertThat(instruction, instanceOf(IndexedNameEntryInstruction.class));
|
||||||
assertThat(((IndexedNameEntryInstruction)instruction).getIndex(), is(1));
|
assertThat(((IndexedNameEntryInstruction)instruction).getIndex(), is(1));
|
||||||
assertThat(((IndexedNameEntryInstruction)instruction).getValue(), is("/sample/path"));
|
assertThat(((IndexedNameEntryInstruction)instruction).getValue(), is("/sample/path"));
|
||||||
assertThat(toString(instruction), equalsHex("c10c 2f73 616d 706c 652f 7061 7468"));
|
assertThat(QpackTestUtil.toHexString(instruction), QpackTestUtil.equalsHex("c10c 2f73 616d 706c 652f 7061 7468"));
|
||||||
assertTrue(_encoderHandler.isEmpty());
|
assertTrue(_encoderHandler.isEmpty());
|
||||||
|
|
||||||
// We cannot decode the buffer until we parse the two instructions generated above (we reach required insert count).
|
// We cannot decode the buffer until we parse the two instructions generated above (we reach required insert count).
|
||||||
_decoder.decode(streamId, buffer);
|
_decoder.decode(streamId, buffer);
|
||||||
assertNull(_decoderHandler.getHttpFields());
|
assertNull(_decoderHandler.getHttpFields());
|
||||||
|
|
||||||
_decoderInstructionParser.parse(toHex("c00f 7777 772e 6578 616d 706c 652e 636f 6d"));
|
_decoderInstructionParser.parse(QpackTestUtil.hexToBuffer("c00f 7777 772e 6578 616d 706c 652e 636f 6d"));
|
||||||
assertNull(_decoderHandler.getHttpFields());
|
assertNull(_decoderHandler.getHttpFields());
|
||||||
assertThat(_decoderHandler.getInstruction(), instanceOf(InsertCountIncrementInstruction.class));
|
assertThat(_decoderHandler.getInstruction(), instanceOf(InsertCountIncrementInstruction.class));
|
||||||
|
|
||||||
_decoderInstructionParser.parse(toHex("c10c 2f73 616d 706c 652f 7061 7468"));
|
_decoderInstructionParser.parse(QpackTestUtil.hexToBuffer("c10c 2f73 616d 706c 652f 7061 7468"));
|
||||||
assertThat(_decoderHandler.getHttpFields(), is(httpFields));
|
assertThat(_decoderHandler.getHttpFields(), is(httpFields));
|
||||||
assertThat(_decoderHandler.getInstruction(), instanceOf(InsertCountIncrementInstruction.class));
|
assertThat(_decoderHandler.getInstruction(), instanceOf(InsertCountIncrementInstruction.class));
|
||||||
|
|
||||||
|
@ -138,39 +132,14 @@ public class EncodeDecodeTest
|
||||||
assertTrue(_decoderHandler.isEmpty());
|
assertTrue(_decoderHandler.isEmpty());
|
||||||
|
|
||||||
// Parse the decoder instructions on the encoder.
|
// Parse the decoder instructions on the encoder.
|
||||||
_encoderInstructionParser.parse(toBuffer(new InsertCountIncrementInstruction(2)));
|
_encoderInstructionParser.parse(QpackTestUtil.toBuffer(new InsertCountIncrementInstruction(2)));
|
||||||
_encoderInstructionParser.parse(toBuffer(new SectionAcknowledgmentInstruction(streamId)));
|
_encoderInstructionParser.parse(QpackTestUtil.toBuffer(new SectionAcknowledgmentInstruction(streamId)));
|
||||||
|
|
||||||
// B.3. Speculative Insert
|
// B.3. Speculative Insert
|
||||||
_encoder.insert(new HttpField("custom-key", "custom-value"));
|
_encoder.insert(new HttpField("custom-key", "custom-value"));
|
||||||
instruction = _encoderHandler.getInstruction();
|
instruction = _encoderHandler.getInstruction();
|
||||||
assertThat(instruction, instanceOf(LiteralNameEntryInstruction.class));
|
assertThat(instruction, instanceOf(LiteralNameEntryInstruction.class));
|
||||||
assertThat(toString(instruction), equalsHex("4a63 7573 746f 6d2d 6b65 790c 6375 7374 6f6d 2d76 616c 7565"));
|
assertThat(QpackTestUtil.toHexString(instruction), QpackTestUtil.equalsHex("4a63 7573 746f 6d2d 6b65 790c 6375 7374 6f6d 2d76 616c 7565"));
|
||||||
_encoder.insertCountIncrement(1);
|
_encoder.insertCountIncrement(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ByteBuffer toBuffer(Instruction instruction)
|
|
||||||
{
|
|
||||||
ByteBufferPool.Lease lease = new ByteBufferPool.Lease(new NullByteBufferPool());
|
|
||||||
instruction.encode(lease);
|
|
||||||
assertThat(lease.getSize(), is(1));
|
|
||||||
return lease.getByteBuffers().get(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String toString(Instruction instruction)
|
|
||||||
{
|
|
||||||
return BufferUtil.toHexString(toBuffer(instruction));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ByteBuffer toHex(String hexString)
|
|
||||||
{
|
|
||||||
hexString = hexString.replaceAll("\\s+", "");
|
|
||||||
return ByteBuffer.wrap(TypeUtil.fromHexString(hexString));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Matcher<java.lang.String> equalsHex(String expectedString)
|
|
||||||
{
|
|
||||||
expectedString = expectedString.replaceAll("\\s+", "");
|
|
||||||
return org.hamcrest.text.IsEqualIgnoringCase.equalToIgnoringCase(expectedString);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,103 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// 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.nio.ByteBuffer;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.http.HttpField;
|
||||||
|
import org.eclipse.jetty.http.HttpFields;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
|
||||||
|
public class EvictionTest
|
||||||
|
{
|
||||||
|
private QpackEncoder _encoder;
|
||||||
|
private QpackDecoder _decoder;
|
||||||
|
private final TestDecoderHandler _decoderHandler = new TestDecoderHandler();
|
||||||
|
private final TestEncoderHandler _encoderHandler = new TestEncoderHandler();
|
||||||
|
private final Random random = new Random();
|
||||||
|
|
||||||
|
private static final int MAX_BLOCKED_STREAMS = 5;
|
||||||
|
private static final int MAX_HEADER_SIZE = 1024;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void before()
|
||||||
|
{
|
||||||
|
_decoder = new QpackDecoder(_decoderHandler, MAX_HEADER_SIZE);
|
||||||
|
_encoder = new QpackEncoder(_encoderHandler, MAX_BLOCKED_STREAMS)
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected boolean shouldHuffmanEncode(HttpField httpField)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set the instruction bytes to be passed on to the remote Encoder/Decoder through the handler directly.
|
||||||
|
_encoderHandler.setDecoder(_decoder);
|
||||||
|
_decoderHandler.setEncoder(_encoder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() throws Exception
|
||||||
|
{
|
||||||
|
_encoder.setCapacity(1024);
|
||||||
|
|
||||||
|
for (int i = 0; i < 10000; i++)
|
||||||
|
{
|
||||||
|
HttpFields httpFields = newRandomFields(5);
|
||||||
|
int streamId = getPositiveInt(10);
|
||||||
|
ByteBuffer encodedFields = _encoder.encode(streamId, httpFields);
|
||||||
|
_decoder.decode(streamId, encodedFields);
|
||||||
|
HttpFields result = _decoderHandler.getHttpFields();
|
||||||
|
|
||||||
|
System.err.println("encoder: ");
|
||||||
|
System.err.println(_encoder.dump());
|
||||||
|
System.err.println();
|
||||||
|
System.err.println("decoder: ");
|
||||||
|
System.err.println(_decoder.dump());
|
||||||
|
System.err.println();
|
||||||
|
System.err.println("====================");
|
||||||
|
System.err.println();
|
||||||
|
|
||||||
|
assertThat(result, is(httpFields));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpFields newRandomFields(int size)
|
||||||
|
{
|
||||||
|
HttpFields.Mutable fields = HttpFields.build();
|
||||||
|
for (int i = 0; i < size; i++)
|
||||||
|
{
|
||||||
|
fields.add(newRandomField());
|
||||||
|
}
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpField newRandomField()
|
||||||
|
{
|
||||||
|
String header = "header" + getPositiveInt(999);
|
||||||
|
String value = "value" + getPositiveInt(999);
|
||||||
|
return new HttpField(header, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPositiveInt(int max)
|
||||||
|
{
|
||||||
|
return Math.abs(random.nextInt(max));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// 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.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.http3.qpack.generator.Instruction;
|
||||||
|
import org.eclipse.jetty.io.ByteBufferPool;
|
||||||
|
import org.eclipse.jetty.io.NullByteBufferPool;
|
||||||
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
|
import org.eclipse.jetty.util.TypeUtil;
|
||||||
|
import org.hamcrest.Matcher;
|
||||||
|
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
|
||||||
|
public class QpackTestUtil
|
||||||
|
{
|
||||||
|
public static Matcher<String> equalsHex(String expectedString)
|
||||||
|
{
|
||||||
|
expectedString = expectedString.replaceAll("\\s+", "");
|
||||||
|
return org.hamcrest.text.IsEqualIgnoringCase.equalToIgnoringCase(expectedString);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ByteBuffer toBuffer(Instruction instruction)
|
||||||
|
{
|
||||||
|
ByteBufferPool.Lease lease = new ByteBufferPool.Lease(new NullByteBufferPool());
|
||||||
|
instruction.encode(lease);
|
||||||
|
assertThat(lease.getSize(), is(1));
|
||||||
|
return lease.getByteBuffers().get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ByteBuffer hexToBuffer(String hexString)
|
||||||
|
{
|
||||||
|
hexString = hexString.replaceAll("\\s+", "");
|
||||||
|
return ByteBuffer.wrap(TypeUtil.fromHexString(hexString));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String toHexString(Instruction instruction)
|
||||||
|
{
|
||||||
|
return BufferUtil.toHexString(toBuffer(instruction));
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,6 +23,12 @@ public class TestDecoderHandler implements QpackDecoder.Handler
|
||||||
{
|
{
|
||||||
private final Queue<HttpFields> _httpFieldsList = new LinkedList<>();
|
private final Queue<HttpFields> _httpFieldsList = new LinkedList<>();
|
||||||
private final Queue<Instruction> _instructionList = new LinkedList<>();
|
private final Queue<Instruction> _instructionList = new LinkedList<>();
|
||||||
|
private QpackEncoder _encoder;
|
||||||
|
|
||||||
|
public void setEncoder(QpackEncoder encoder)
|
||||||
|
{
|
||||||
|
_encoder = encoder;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onHttpFields(int streamId, HttpFields httpFields)
|
public void onHttpFields(int streamId, HttpFields httpFields)
|
||||||
|
@ -31,9 +37,11 @@ public class TestDecoderHandler implements QpackDecoder.Handler
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onInstruction(Instruction instruction)
|
public void onInstruction(Instruction instruction) throws QpackException
|
||||||
{
|
{
|
||||||
_instructionList.add(instruction);
|
_instructionList.add(instruction);
|
||||||
|
if (_encoder != null)
|
||||||
|
_encoder.parseInstruction(QpackTestUtil.toBuffer(instruction));
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpFields getHttpFields()
|
public HttpFields getHttpFields()
|
||||||
|
|
|
@ -21,11 +21,19 @@ import org.eclipse.jetty.http3.qpack.generator.Instruction;
|
||||||
public class TestEncoderHandler implements QpackEncoder.Handler
|
public class TestEncoderHandler implements QpackEncoder.Handler
|
||||||
{
|
{
|
||||||
private final Queue<Instruction> _instructionList = new LinkedList<>();
|
private final Queue<Instruction> _instructionList = new LinkedList<>();
|
||||||
|
private QpackDecoder _decoder;
|
||||||
|
|
||||||
|
public void setDecoder(QpackDecoder decoder)
|
||||||
|
{
|
||||||
|
_decoder = decoder;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onInstruction(Instruction instruction)
|
public void onInstruction(Instruction instruction) throws QpackException
|
||||||
{
|
{
|
||||||
_instructionList.add(instruction);
|
_instructionList.add(instruction);
|
||||||
|
if (_decoder != null)
|
||||||
|
_decoder.parseInstruction(QpackTestUtil.toBuffer(instruction));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Instruction getInstruction()
|
public Instruction getInstruction()
|
||||||
|
|
Loading…
Reference in New Issue