Renaming and change package access.

Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
This commit is contained in:
Lachlan Roberts 2021-03-08 21:23:34 +11:00 committed by Simone Bordet
parent da50072cc8
commit a4938a3f4a
7 changed files with 380 additions and 379 deletions

View File

@ -16,7 +16,7 @@ package org.eclipse.jetty.http3.qpack;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.Queue; import java.util.Queue;
public class StreamInfo class StreamInfo
{ {
private final int _streamId; private final int _streamId;
private final Queue<SectionInfo> _sectionInfos = new LinkedList<>(); private final Queue<SectionInfo> _sectionInfos = new LinkedList<>();

View File

@ -15,75 +15,95 @@ 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.QpackDecoder;
import org.eclipse.jetty.http3.qpack.QpackException; import org.eclipse.jetty.http3.qpack.QpackException;
/** /**
* Receives instructions coming from the remote Encoder as a sequence of unframed instructions. * Parses a stream of unframed instructions for the Decoder. These instructions are sent from the remote Encoder.
*/ */
public class DecoderInstructionParser public class DecoderInstructionParser
{ {
private static final int SECTION_ACKNOWLEDGEMENT_PREFIX = 7;
private static final int STREAM_CANCELLATION_PREFIX = 6;
private static final int INSERT_COUNT_INCREMENT_PREFIX = 6;
private final Handler _handler; private final Handler _handler;
private final NBitStringParser _stringParser;
private final NBitIntegerParser _integerParser; private final NBitIntegerParser _integerParser;
private State _state = State.IDLE; private State _state = State.PARSING;
private Operation _operation = Operation.NONE;
private boolean _referenceDynamicTable;
private int _index;
private String _name;
private enum State private enum State
{ {
IDLE, PARSING,
SECTION_ACKNOWLEDGEMENT, SET_CAPACITY,
STREAM_CANCELLATION, REFERENCED_NAME,
INSERT_COUNT_INCREMENT LITERAL_NAME,
DUPLICATE
}
private enum Operation
{
NONE,
INDEX,
NAME,
VALUE,
} }
public interface Handler public interface Handler
{ {
void onSectionAcknowledgement(int streamId) throws QpackException; void onSetDynamicTableCapacity(int capacity) throws QpackException;
void onStreamCancellation(int streamId) throws QpackException; void onDuplicate(int index) throws QpackException;
void onInsertCountIncrement(int increment) throws QpackException; void onInsertNameWithReference(int nameIndex, boolean isDynamicTableIndex, String value) throws QpackException;
void onInsertWithLiteralName(String name, String value) throws QpackException;
} }
public static class EncoderHandler implements Handler public static class DecoderAdapter implements Handler
{ {
private final QpackEncoder _encoder; private final QpackDecoder _decoder;
public EncoderHandler(QpackEncoder encoder) public DecoderAdapter(QpackDecoder decoder)
{ {
_encoder = encoder; _decoder = decoder;
} }
@Override @Override
public void onSectionAcknowledgement(int streamId) throws QpackException public void onSetDynamicTableCapacity(int capacity)
{ {
_encoder.sectionAcknowledgement(streamId); _decoder.setCapacity(capacity);
} }
@Override @Override
public void onStreamCancellation(int streamId) throws QpackException public void onDuplicate(int index) throws QpackException
{ {
_encoder.streamCancellation(streamId); _decoder.insert(index);
} }
@Override @Override
public void onInsertCountIncrement(int increment) throws QpackException public void onInsertNameWithReference(int nameIndex, boolean isDynamicTableIndex, String value) throws QpackException
{ {
_encoder.insertCountIncrement(increment); _decoder.insert(nameIndex, isDynamicTableIndex, value);
}
@Override
public void onInsertWithLiteralName(String name, String value) throws QpackException
{
_decoder.insert(name, value);
} }
} }
public DecoderInstructionParser(QpackEncoder encoder) public DecoderInstructionParser(QpackDecoder decoder)
{ {
this(new EncoderHandler(encoder)); this(new DecoderAdapter(decoder));
} }
public DecoderInstructionParser(Handler handler) public DecoderInstructionParser(Handler handler)
{ {
_handler = handler; _handler = handler;
_stringParser = new NBitStringParser();
_integerParser = new NBitIntegerParser(); _integerParser = new NBitIntegerParser();
} }
@ -94,39 +114,46 @@ public class DecoderInstructionParser
switch (_state) switch (_state)
{ {
case IDLE: case PARSING:
// Get first byte without incrementing the buffers position.
byte firstByte = buffer.get(buffer.position()); byte firstByte = buffer.get(buffer.position());
if ((firstByte & 0x80) != 0) if ((firstByte & 0x80) != 0)
{ {
_state = State.SECTION_ACKNOWLEDGEMENT; _state = State.REFERENCED_NAME;
_integerParser.setPrefix(SECTION_ACKNOWLEDGEMENT_PREFIX); parseInsertNameWithReference(buffer);
parseSectionAcknowledgment(buffer);
} }
else if ((firstByte & 0x40) != 0) else if ((firstByte & 0x40) != 0)
{ {
_state = State.STREAM_CANCELLATION; _state = State.LITERAL_NAME;
_integerParser.setPrefix(STREAM_CANCELLATION_PREFIX); parseInsertWithLiteralName(buffer);
parseStreamCancellation(buffer); }
else if ((firstByte & 0x20) != 0)
{
_state = State.SET_CAPACITY;
_integerParser.setPrefix(5);
parseSetDynamicTableCapacity(buffer);
} }
else else
{ {
_state = State.INSERT_COUNT_INCREMENT; _state = State.DUPLICATE;
_integerParser.setPrefix(INSERT_COUNT_INCREMENT_PREFIX); _integerParser.setPrefix(5);
parseInsertCountIncrement(buffer); parseDuplicate(buffer);
} }
break; break;
case SECTION_ACKNOWLEDGEMENT: case SET_CAPACITY:
parseSectionAcknowledgment(buffer); parseSetDynamicTableCapacity(buffer);
break; break;
case STREAM_CANCELLATION: case DUPLICATE:
parseStreamCancellation(buffer); parseDuplicate(buffer);
break; break;
case INSERT_COUNT_INCREMENT: case LITERAL_NAME:
parseInsertCountIncrement(buffer); parseInsertWithLiteralName(buffer);
break;
case REFERENCED_NAME:
parseInsertNameWithReference(buffer);
break; break;
default: default:
@ -134,39 +161,109 @@ public class DecoderInstructionParser
} }
} }
private void parseSectionAcknowledgment(ByteBuffer buffer) throws QpackException private void parseInsertNameWithReference(ByteBuffer buffer) throws QpackException
{ {
int streamId = _integerParser.decode(buffer); while (true)
if (streamId >= 0)
{ {
reset(); switch (_operation)
_handler.onSectionAcknowledgement(streamId); {
case NONE:
byte firstByte = buffer.get(buffer.position());
_referenceDynamicTable = (firstByte & 0x40) == 0;
_operation = Operation.INDEX;
_integerParser.setPrefix(6);
continue;
case INDEX:
_index = _integerParser.decode(buffer);
if (_index < 0)
return;
_operation = Operation.VALUE;
_stringParser.setPrefix(8);
continue;
case VALUE:
String value = _stringParser.decode(buffer);
if (value == null)
return;
int index = _index;
boolean dynamic = _referenceDynamicTable;
reset();
_handler.onInsertNameWithReference(index, dynamic, value);
return;
default:
throw new IllegalStateException(_operation.name());
}
} }
} }
private void parseStreamCancellation(ByteBuffer buffer) throws QpackException private void parseInsertWithLiteralName(ByteBuffer buffer) throws QpackException
{ {
int streamId = _integerParser.decode(buffer); while (true)
if (streamId >= 0)
{ {
reset(); switch (_operation)
_handler.onStreamCancellation(streamId); {
case NONE:
_operation = Operation.NAME;
_stringParser.setPrefix(6);
continue;
case NAME:
_name = _stringParser.decode(buffer);
if (_name == null)
return;
_operation = Operation.VALUE;
_stringParser.setPrefix(8);
continue;
case VALUE:
String value = _stringParser.decode(buffer);
if (value == null)
return;
String name = _name;
reset();
_handler.onInsertWithLiteralName(name, value);
return;
default:
throw new IllegalStateException(_operation.name());
}
} }
} }
private void parseInsertCountIncrement(ByteBuffer buffer) throws QpackException private void parseDuplicate(ByteBuffer buffer) throws QpackException
{ {
int increment = _integerParser.decode(buffer); int index = _integerParser.decode(buffer);
if (increment >= 0) if (index >= 0)
{ {
reset(); reset();
_handler.onInsertCountIncrement(increment); _handler.onDuplicate(index);
}
}
private void parseSetDynamicTableCapacity(ByteBuffer buffer) throws QpackException
{
int capacity = _integerParser.decode(buffer);
if (capacity >= 0)
{
reset();
_handler.onSetDynamicTableCapacity(capacity);
} }
} }
public void reset() public void reset()
{ {
_state = State.IDLE; _stringParser.reset();
_integerParser.reset(); _integerParser.reset();
_state = State.PARSING;
_operation = Operation.NONE;
_referenceDynamicTable = false;
_index = -1;
_name = null;
} }
} }

View File

@ -223,6 +223,7 @@ public class EncodedFieldSection
private class IndexedNameField implements EncodedField private class IndexedNameField implements EncodedField
{ {
// TODO: what to do with allow encoding?
private final boolean _allowEncoding; private final boolean _allowEncoding;
private final boolean _dynamicTable; private final boolean _dynamicTable;
private final int _nameIndex; private final int _nameIndex;
@ -243,7 +244,7 @@ public class EncodedFieldSection
if (_dynamicTable) if (_dynamicTable)
field = context.getDynamicTable().getAbsolute(_base + _nameIndex + 1).getHttpField(); field = context.getDynamicTable().getAbsolute(_base + _nameIndex + 1).getHttpField();
else else
field = context.getStaticTable().get(_nameIndex).getHttpField(); field = QpackContext.getStaticTable().get(_nameIndex).getHttpField();
return new HttpField(field.getHeader(), field.getName(), _value); return new HttpField(field.getHeader(), field.getName(), _value);
} }

View File

@ -15,95 +15,75 @@ 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.QpackEncoder;
import org.eclipse.jetty.http3.qpack.QpackException; import org.eclipse.jetty.http3.qpack.QpackException;
/** /**
* Receives instructions coming from the remote Decoder as a sequence of unframed instructions. * Parses a stream of unframed instructions for the Encoder. These instructions are sent from the remote Decoder.
*/ */
public class EncoderInstructionParser public class EncoderInstructionParser
{ {
private final Handler _handler; private static final int SECTION_ACKNOWLEDGEMENT_PREFIX = 7;
private final NBitStringParser _stringParser; private static final int STREAM_CANCELLATION_PREFIX = 6;
private final NBitIntegerParser _integerParser; private static final int INSERT_COUNT_INCREMENT_PREFIX = 6;
private State _state = State.PARSING;
private Operation _operation = Operation.NONE;
private boolean _referenceDynamicTable; private final Handler _handler;
private int _index; private final NBitIntegerParser _integerParser;
private String _name; private State _state = State.IDLE;
private enum State private enum State
{ {
PARSING, IDLE,
SET_CAPACITY, SECTION_ACKNOWLEDGEMENT,
REFERENCED_NAME, STREAM_CANCELLATION,
LITERAL_NAME, INSERT_COUNT_INCREMENT
DUPLICATE
}
private enum Operation
{
NONE,
INDEX,
NAME,
VALUE,
} }
public interface Handler public interface Handler
{ {
void onSetDynamicTableCapacity(int capacity) throws QpackException; void onSectionAcknowledgement(int streamId) throws QpackException;
void onDuplicate(int index) throws QpackException; void onStreamCancellation(int streamId) throws QpackException;
void onInsertNameWithReference(int nameIndex, boolean isDynamicTableIndex, String value) throws QpackException; void onInsertCountIncrement(int increment) throws QpackException;
void onInsertWithLiteralName(String name, String value) throws QpackException;
} }
public static class DecoderHandler implements Handler public static class EncoderAdapter implements Handler
{ {
private final QpackDecoder _decoder; private final QpackEncoder _encoder;
public DecoderHandler(QpackDecoder decoder) public EncoderAdapter(QpackEncoder encoder)
{ {
_decoder = decoder; _encoder = encoder;
} }
@Override @Override
public void onSetDynamicTableCapacity(int capacity) public void onSectionAcknowledgement(int streamId) throws QpackException
{ {
_decoder.setCapacity(capacity); _encoder.sectionAcknowledgement(streamId);
} }
@Override @Override
public void onDuplicate(int index) throws QpackException public void onStreamCancellation(int streamId) throws QpackException
{ {
_decoder.insert(index); _encoder.streamCancellation(streamId);
} }
@Override @Override
public void onInsertNameWithReference(int nameIndex, boolean isDynamicTableIndex, String value) throws QpackException public void onInsertCountIncrement(int increment) throws QpackException
{ {
_decoder.insert(nameIndex, isDynamicTableIndex, value); _encoder.insertCountIncrement(increment);
}
@Override
public void onInsertWithLiteralName(String name, String value) throws QpackException
{
_decoder.insert(name, value);
} }
} }
public EncoderInstructionParser(QpackDecoder decoder) public EncoderInstructionParser(QpackEncoder encoder)
{ {
this(new DecoderHandler(decoder)); this(new EncoderAdapter(encoder));
} }
public EncoderInstructionParser(Handler handler) public EncoderInstructionParser(Handler handler)
{ {
_handler = handler; _handler = handler;
_stringParser = new NBitStringParser();
_integerParser = new NBitIntegerParser(); _integerParser = new NBitIntegerParser();
} }
@ -114,46 +94,39 @@ public class EncoderInstructionParser
switch (_state) switch (_state)
{ {
case PARSING: case IDLE:
// Get first byte without incrementing the buffers position.
byte firstByte = buffer.get(buffer.position()); byte firstByte = buffer.get(buffer.position());
if ((firstByte & 0x80) != 0) if ((firstByte & 0x80) != 0)
{ {
_state = State.REFERENCED_NAME; _state = State.SECTION_ACKNOWLEDGEMENT;
parseInsertNameWithReference(buffer); _integerParser.setPrefix(SECTION_ACKNOWLEDGEMENT_PREFIX);
parseSectionAcknowledgment(buffer);
} }
else if ((firstByte & 0x40) != 0) else if ((firstByte & 0x40) != 0)
{ {
_state = State.LITERAL_NAME; _state = State.STREAM_CANCELLATION;
parseInsertWithLiteralName(buffer); _integerParser.setPrefix(STREAM_CANCELLATION_PREFIX);
} parseStreamCancellation(buffer);
else if ((firstByte & 0x20) != 0)
{
_state = State.SET_CAPACITY;
_integerParser.setPrefix(5);
parseSetDynamicTableCapacity(buffer);
} }
else else
{ {
_state = State.DUPLICATE; _state = State.INSERT_COUNT_INCREMENT;
_integerParser.setPrefix(5); _integerParser.setPrefix(INSERT_COUNT_INCREMENT_PREFIX);
parseDuplicate(buffer); parseInsertCountIncrement(buffer);
} }
break; break;
case SET_CAPACITY: case SECTION_ACKNOWLEDGEMENT:
parseSetDynamicTableCapacity(buffer); parseSectionAcknowledgment(buffer);
break; break;
case DUPLICATE: case STREAM_CANCELLATION:
parseDuplicate(buffer); parseStreamCancellation(buffer);
break; break;
case LITERAL_NAME: case INSERT_COUNT_INCREMENT:
parseInsertWithLiteralName(buffer); parseInsertCountIncrement(buffer);
break;
case REFERENCED_NAME:
parseInsertNameWithReference(buffer);
break; break;
default: default:
@ -161,109 +134,39 @@ public class EncoderInstructionParser
} }
} }
private void parseInsertNameWithReference(ByteBuffer buffer) throws QpackException private void parseSectionAcknowledgment(ByteBuffer buffer) throws QpackException
{ {
while (true) int streamId = _integerParser.decode(buffer);
{ if (streamId >= 0)
switch (_operation)
{
case NONE:
byte firstByte = buffer.get(buffer.position());
_referenceDynamicTable = (firstByte & 0x40) == 0;
_operation = Operation.INDEX;
_integerParser.setPrefix(6);
continue;
case INDEX:
_index = _integerParser.decode(buffer);
if (_index < 0)
return;
_operation = Operation.VALUE;
_stringParser.setPrefix(8);
continue;
case VALUE:
String value = _stringParser.decode(buffer);
if (value == null)
return;
int index = _index;
boolean dynamic = _referenceDynamicTable;
reset();
_handler.onInsertNameWithReference(index, dynamic, value);
return;
default:
throw new IllegalStateException(_operation.name());
}
}
}
private void parseInsertWithLiteralName(ByteBuffer buffer) throws QpackException
{
while (true)
{
switch (_operation)
{
case NONE:
_operation = Operation.NAME;
_stringParser.setPrefix(6);
continue;
case NAME:
_name = _stringParser.decode(buffer);
if (_name == null)
return;
_operation = Operation.VALUE;
_stringParser.setPrefix(8);
continue;
case VALUE:
String value = _stringParser.decode(buffer);
if (value == null)
return;
String name = _name;
reset();
_handler.onInsertWithLiteralName(name, value);
return;
default:
throw new IllegalStateException(_operation.name());
}
}
}
private void parseDuplicate(ByteBuffer buffer) throws QpackException
{
int index = _integerParser.decode(buffer);
if (index >= 0)
{ {
reset(); reset();
_handler.onDuplicate(index); _handler.onSectionAcknowledgement(streamId);
} }
} }
private void parseSetDynamicTableCapacity(ByteBuffer buffer) throws QpackException private void parseStreamCancellation(ByteBuffer buffer) throws QpackException
{ {
int capacity = _integerParser.decode(buffer); int streamId = _integerParser.decode(buffer);
if (capacity >= 0) if (streamId >= 0)
{ {
reset(); reset();
_handler.onSetDynamicTableCapacity(capacity); _handler.onStreamCancellation(streamId);
}
}
private void parseInsertCountIncrement(ByteBuffer buffer) throws QpackException
{
int increment = _integerParser.decode(buffer);
if (increment >= 0)
{
reset();
_handler.onInsertCountIncrement(increment);
} }
} }
public void reset() public void reset()
{ {
_stringParser.reset(); _state = State.IDLE;
_integerParser.reset(); _integerParser.reset();
_state = State.PARSING;
_operation = Operation.NONE;
_referenceDynamicTable = false;
_index = -1;
_name = null;
} }
} }

View File

@ -20,114 +20,118 @@ import java.util.Queue;
import org.eclipse.jetty.http3.qpack.parser.DecoderInstructionParser; import org.eclipse.jetty.http3.qpack.parser.DecoderInstructionParser;
import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.util.TypeUtil;
import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
public class DecoderInstructionParserTest public class DecoderInstructionParserTest
{ {
public static class DebugHandler implements DecoderInstructionParser.Handler public static class DebugHandler implements DecoderInstructionParser.Handler
{ {
public Queue<Integer> sectionAcknowledgements = new LinkedList<>(); public Queue<Integer> setCapacities = new LinkedList<>();
public Queue<Integer> streamCancellations = new LinkedList<>(); public Queue<Integer> duplicates = new LinkedList<>();
public Queue<Integer> insertCountIncrements = new LinkedList<>(); public Queue<Entry> literalNameEntries = new LinkedList<>();
public Queue<ReferencedEntry> referencedNameEntries = new LinkedList<>();
@Override public static class Entry
public void onSectionAcknowledgement(int streamId)
{ {
sectionAcknowledgements.add(streamId); public Entry(String name, String value)
{
this.value = value;
this.name = name;
}
String name;
String value;
}
public static class ReferencedEntry
{
public ReferencedEntry(int index, boolean dynamic, String value)
{
this.index = index;
this.dynamic = dynamic;
this.value = value;
}
int index;
boolean dynamic;
String value;
} }
@Override @Override
public void onStreamCancellation(int streamId) public void onSetDynamicTableCapacity(int capacity)
{ {
streamCancellations.add(streamId); setCapacities.add(capacity);
} }
@Override @Override
public void onInsertCountIncrement(int increment) public void onDuplicate(int index)
{ {
insertCountIncrements.add(increment); duplicates.add(index);
}
@Override
public void onInsertNameWithReference(int nameIndex, boolean isDynamicTableIndex, String value)
{
referencedNameEntries.add(new ReferencedEntry(nameIndex, isDynamicTableIndex, value));
}
@Override
public void onInsertWithLiteralName(String name, String value)
{
literalNameEntries.add(new Entry(name, value));
} }
public boolean isEmpty() public boolean isEmpty()
{ {
return sectionAcknowledgements.isEmpty() && streamCancellations.isEmpty() && insertCountIncrements.isEmpty(); return setCapacities.isEmpty() && duplicates.isEmpty() && literalNameEntries.isEmpty() && referencedNameEntries.isEmpty();
} }
} }
@Test private DecoderInstructionParser _instructionParser;
public void testSectionAcknowledgement() throws Exception private DebugHandler _handler;
@BeforeEach
public void before()
{ {
DebugHandler debugHandler = new DebugHandler(); _handler = new DebugHandler();
DecoderInstructionParser incomingEncoderStream = new DecoderInstructionParser(debugHandler); _instructionParser = new DecoderInstructionParser(_handler);
// Example from the spec, section acknowledgement instruction with stream id 4.
String encoded = "84";
ByteBuffer buffer = BufferUtil.toBuffer(TypeUtil.fromHexString(encoded));
incomingEncoderStream.parse(buffer);
assertThat(debugHandler.sectionAcknowledgements.poll(), is(4));
assertTrue(debugHandler.isEmpty());
// 1111 1110 == FE is largest value we can do without continuation should be stream ID 126.
encoded = "FE";
buffer = BufferUtil.toBuffer(TypeUtil.fromHexString(encoded));
incomingEncoderStream.parse(buffer);
assertThat(debugHandler.sectionAcknowledgements.poll(), is(126));
assertTrue(debugHandler.isEmpty());
// 1111 1111 0000 0000 == FF00 is next value, stream id 127.
encoded = "FF00";
buffer = BufferUtil.toBuffer(TypeUtil.fromHexString(encoded));
incomingEncoderStream.parse(buffer);
assertThat(debugHandler.sectionAcknowledgements.poll(), is(127));
assertTrue(debugHandler.isEmpty());
// 1111 1111 0000 0001 == FF01 is next value, stream id 128.
encoded = "FF01";
buffer = BufferUtil.toBuffer(TypeUtil.fromHexString(encoded));
incomingEncoderStream.parse(buffer);
assertThat(debugHandler.sectionAcknowledgements.poll(), is(128));
assertTrue(debugHandler.isEmpty());
// FFBA09 contains section ack with stream ID of 1337, this contains an octet with continuation bit.
encoded = "FFBA09";
buffer = BufferUtil.toBuffer(TypeUtil.fromHexString(encoded));
incomingEncoderStream.parse(buffer);
assertThat(debugHandler.sectionAcknowledgements.poll(), is(1337));
assertTrue(debugHandler.isEmpty());
// Test with continuation.
encoded = "FFBA09";
byte[] bytes = TypeUtil.fromHexString(encoded);
for (int i = 0; i < 10; i++)
{
ByteBuffer buffer1 = BufferUtil.toBuffer(bytes, 0, 2);
ByteBuffer buffer2 = BufferUtil.toBuffer(bytes, 2, 1);
incomingEncoderStream.parse(buffer1);
assertTrue(debugHandler.isEmpty());
incomingEncoderStream.parse(buffer2);
assertThat(debugHandler.sectionAcknowledgements.poll(), is(1337));
assertTrue(debugHandler.isEmpty());
}
} }
@Disabled
@Test @Test
public void testStreamCancellation() throws Exception public void testAddWithReferencedEntry() throws Exception
{ {
// TODO: Write this test. String insertAuthorityEntry = "c00f7777772e6578616d706c652e636f6d";
throw new RuntimeException("TODO: testStreamCancellation"); ByteBuffer buffer = BufferUtil.toBuffer(TypeUtil.fromHexString(insertAuthorityEntry));
_instructionParser.parse(buffer);
DebugHandler.ReferencedEntry entry = _handler.referencedNameEntries.poll();
assertNotNull(entry);
assertThat(entry.index, is(0));
assertThat(entry.dynamic, is(false));
assertThat(entry.value, is("www.example.com"));
String insertPathEntry = "c10c2f73616d706c652f70617468";
buffer = BufferUtil.toBuffer(TypeUtil.fromHexString(insertPathEntry));
_instructionParser.parse(buffer);
entry = _handler.referencedNameEntries.poll();
assertNotNull(entry);
assertThat(entry.index, is(1));
assertThat(entry.dynamic, is(false));
assertThat(entry.value, is("/sample/path"));
} }
@Disabled
@Test @Test
public void testInsertCountIncrement() throws Exception public void testSetCapacity() throws Exception
{ {
// TODO: Write this test. String setCapacityString = "3fbd01";
throw new RuntimeException("TODO: testInsertCountIncrement"); ByteBuffer buffer = BufferUtil.toBuffer(TypeUtil.fromHexString(setCapacityString));
_instructionParser.parse(buffer);
assertThat(_handler.setCapacities.poll(), is(220));
assertTrue(_handler.isEmpty());
} }
} }

View File

@ -46,8 +46,8 @@ public class EncodeDecodeTest
private TestDecoderHandler _decoderHandler; private TestDecoderHandler _decoderHandler;
private TestEncoderHandler _encoderHandler; private TestEncoderHandler _encoderHandler;
private DecoderInstructionParser _decoderInstructionParser;
private EncoderInstructionParser _encoderInstructionParser; private EncoderInstructionParser _encoderInstructionParser;
private DecoderInstructionParser _decoderInstructionParser;
private static final int MAX_BLOCKED_STREAMS = 5; private static final int MAX_BLOCKED_STREAMS = 5;
private static final int MAX_HEADER_SIZE = 1024; private static final int MAX_HEADER_SIZE = 1024;
@ -66,8 +66,8 @@ public class EncodeDecodeTest
} }
}; };
_decoder = new QpackDecoder(_decoderHandler, MAX_HEADER_SIZE); _decoder = new QpackDecoder(_decoderHandler, MAX_HEADER_SIZE);
_encoderInstructionParser = new EncoderInstructionParser(_decoder); _encoderInstructionParser = new EncoderInstructionParser(_encoder);
_decoderInstructionParser = new DecoderInstructionParser(_encoder); _decoderInstructionParser = new DecoderInstructionParser(_decoder);
} }
@Test @Test
@ -88,7 +88,7 @@ public class EncodeDecodeTest
assertThat(_decoderHandler.getInstruction(), instanceOf(SectionAcknowledgmentInstruction.class)); assertThat(_decoderHandler.getInstruction(), instanceOf(SectionAcknowledgmentInstruction.class));
assertTrue(_decoderHandler.isEmpty()); assertTrue(_decoderHandler.isEmpty());
_decoderInstructionParser.parse(toBuffer(new SectionAcknowledgmentInstruction(streamId))); _encoderInstructionParser.parse(toBuffer(new SectionAcknowledgmentInstruction(streamId)));
// B.2. Dynamic Table. // B.2. Dynamic Table.
@ -99,7 +99,7 @@ public class EncodeDecodeTest
assertThat(((SetCapacityInstruction)instruction).getCapacity(), is(220)); assertThat(((SetCapacityInstruction)instruction).getCapacity(), is(220));
assertThat(toString(instruction), equalsHex("3fbd01")); assertThat(toString(instruction), equalsHex("3fbd01"));
_encoderInstructionParser.parse(toHex("3fbd01")); _decoderInstructionParser.parse(toHex("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.
@ -126,11 +126,11 @@ public class EncodeDecodeTest
_decoder.decode(streamId, buffer); _decoder.decode(streamId, buffer);
assertNull(_decoderHandler.getHttpFields()); assertNull(_decoderHandler.getHttpFields());
_encoderInstructionParser.parse(toHex("c00f 7777 772e 6578 616d 706c 652e 636f 6d")); _decoderInstructionParser.parse(toHex("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));
_encoderInstructionParser.parse(toHex("c10c 2f73 616d 706c 652f 7061 7468")); _decoderInstructionParser.parse(toHex("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,8 +138,8 @@ public class EncodeDecodeTest
assertTrue(_decoderHandler.isEmpty()); assertTrue(_decoderHandler.isEmpty());
// Parse the decoder instructions on the encoder. // Parse the decoder instructions on the encoder.
_decoderInstructionParser.parse(toBuffer(new InsertCountIncrementInstruction(2))); _encoderInstructionParser.parse(toBuffer(new InsertCountIncrementInstruction(2)));
_decoderInstructionParser.parse(toBuffer(new SectionAcknowledgmentInstruction(streamId))); _encoderInstructionParser.parse(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"));

View File

@ -20,118 +20,114 @@ import java.util.Queue;
import org.eclipse.jetty.http3.qpack.parser.EncoderInstructionParser; import org.eclipse.jetty.http3.qpack.parser.EncoderInstructionParser;
import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.util.TypeUtil;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
public class EncoderInstructionParserTest public class EncoderInstructionParserTest
{ {
public static class DebugHandler implements EncoderInstructionParser.Handler public static class DebugHandler implements EncoderInstructionParser.Handler
{ {
public Queue<Integer> setCapacities = new LinkedList<>(); public Queue<Integer> sectionAcknowledgements = new LinkedList<>();
public Queue<Integer> duplicates = new LinkedList<>(); public Queue<Integer> streamCancellations = new LinkedList<>();
public Queue<Entry> literalNameEntries = new LinkedList<>(); public Queue<Integer> insertCountIncrements = new LinkedList<>();
public Queue<ReferencedEntry> referencedNameEntries = new LinkedList<>();
public static class Entry @Override
public void onSectionAcknowledgement(int streamId)
{ {
public Entry(String name, String value) sectionAcknowledgements.add(streamId);
{
this.value = value;
this.name = name;
}
String name;
String value;
}
public static class ReferencedEntry
{
public ReferencedEntry(int index, boolean dynamic, String value)
{
this.index = index;
this.dynamic = dynamic;
this.value = value;
}
int index;
boolean dynamic;
String value;
} }
@Override @Override
public void onSetDynamicTableCapacity(int capacity) public void onStreamCancellation(int streamId)
{ {
setCapacities.add(capacity); streamCancellations.add(streamId);
} }
@Override @Override
public void onDuplicate(int index) public void onInsertCountIncrement(int increment)
{ {
duplicates.add(index); insertCountIncrements.add(increment);
}
@Override
public void onInsertNameWithReference(int nameIndex, boolean isDynamicTableIndex, String value)
{
referencedNameEntries.add(new ReferencedEntry(nameIndex, isDynamicTableIndex, value));
}
@Override
public void onInsertWithLiteralName(String name, String value)
{
literalNameEntries.add(new Entry(name, value));
} }
public boolean isEmpty() public boolean isEmpty()
{ {
return setCapacities.isEmpty() && duplicates.isEmpty() && literalNameEntries.isEmpty() && referencedNameEntries.isEmpty(); return sectionAcknowledgements.isEmpty() && streamCancellations.isEmpty() && insertCountIncrements.isEmpty();
} }
} }
private EncoderInstructionParser _instructionParser; @Test
private DebugHandler _handler; public void testSectionAcknowledgement() throws Exception
@BeforeEach
public void before()
{ {
_handler = new DebugHandler(); DebugHandler debugHandler = new DebugHandler();
_instructionParser = new EncoderInstructionParser(_handler); EncoderInstructionParser incomingEncoderStream = new EncoderInstructionParser(debugHandler);
// Example from the spec, section acknowledgement instruction with stream id 4.
String encoded = "84";
ByteBuffer buffer = BufferUtil.toBuffer(TypeUtil.fromHexString(encoded));
incomingEncoderStream.parse(buffer);
assertThat(debugHandler.sectionAcknowledgements.poll(), is(4));
assertTrue(debugHandler.isEmpty());
// 1111 1110 == FE is largest value we can do without continuation should be stream ID 126.
encoded = "FE";
buffer = BufferUtil.toBuffer(TypeUtil.fromHexString(encoded));
incomingEncoderStream.parse(buffer);
assertThat(debugHandler.sectionAcknowledgements.poll(), is(126));
assertTrue(debugHandler.isEmpty());
// 1111 1111 0000 0000 == FF00 is next value, stream id 127.
encoded = "FF00";
buffer = BufferUtil.toBuffer(TypeUtil.fromHexString(encoded));
incomingEncoderStream.parse(buffer);
assertThat(debugHandler.sectionAcknowledgements.poll(), is(127));
assertTrue(debugHandler.isEmpty());
// 1111 1111 0000 0001 == FF01 is next value, stream id 128.
encoded = "FF01";
buffer = BufferUtil.toBuffer(TypeUtil.fromHexString(encoded));
incomingEncoderStream.parse(buffer);
assertThat(debugHandler.sectionAcknowledgements.poll(), is(128));
assertTrue(debugHandler.isEmpty());
// FFBA09 contains section ack with stream ID of 1337, this contains an octet with continuation bit.
encoded = "FFBA09";
buffer = BufferUtil.toBuffer(TypeUtil.fromHexString(encoded));
incomingEncoderStream.parse(buffer);
assertThat(debugHandler.sectionAcknowledgements.poll(), is(1337));
assertTrue(debugHandler.isEmpty());
// Test with continuation.
encoded = "FFBA09";
byte[] bytes = TypeUtil.fromHexString(encoded);
for (int i = 0; i < 10; i++)
{
ByteBuffer buffer1 = BufferUtil.toBuffer(bytes, 0, 2);
ByteBuffer buffer2 = BufferUtil.toBuffer(bytes, 2, 1);
incomingEncoderStream.parse(buffer1);
assertTrue(debugHandler.isEmpty());
incomingEncoderStream.parse(buffer2);
assertThat(debugHandler.sectionAcknowledgements.poll(), is(1337));
assertTrue(debugHandler.isEmpty());
}
} }
@Disabled
@Test @Test
public void testAddWithReferencedEntry() throws Exception public void testStreamCancellation() throws Exception
{ {
String insertAuthorityEntry = "c00f7777772e6578616d706c652e636f6d"; // TODO: Write this test.
ByteBuffer buffer = BufferUtil.toBuffer(TypeUtil.fromHexString(insertAuthorityEntry)); throw new RuntimeException("TODO: testStreamCancellation");
_instructionParser.parse(buffer);
DebugHandler.ReferencedEntry entry = _handler.referencedNameEntries.poll();
assertNotNull(entry);
assertThat(entry.index, is(0));
assertThat(entry.dynamic, is(false));
assertThat(entry.value, is("www.example.com"));
String insertPathEntry = "c10c2f73616d706c652f70617468";
buffer = BufferUtil.toBuffer(TypeUtil.fromHexString(insertPathEntry));
_instructionParser.parse(buffer);
entry = _handler.referencedNameEntries.poll();
assertNotNull(entry);
assertThat(entry.index, is(1));
assertThat(entry.dynamic, is(false));
assertThat(entry.value, is("/sample/path"));
} }
@Disabled
@Test @Test
public void testSetCapacity() throws Exception public void testInsertCountIncrement() throws Exception
{ {
String setCapacityString = "3fbd01"; // TODO: Write this test.
ByteBuffer buffer = BufferUtil.toBuffer(TypeUtil.fromHexString(setCapacityString)); throw new RuntimeException("TODO: testInsertCountIncrement");
_instructionParser.parse(buffer);
assertThat(_handler.setCapacities.poll(), is(220));
assertTrue(_handler.isEmpty());
} }
} }