Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
This commit is contained in:
Lachlan Roberts 2021-03-04 18:33:41 +11:00 committed by Simone Bordet
parent f8e2831185
commit 7c042b5205
3 changed files with 88 additions and 9 deletions

View File

@ -129,8 +129,19 @@ public class QpackEncoder
_handler.onInstruction(new SetCapacityInstruction(capacity));
}
public void insertCountIncrement(int increment)
public void insertCountIncrement(int increment) throws QpackException
{
int insertCount = _context.getDynamicTable().getInsertCount();
if (_knownInsertCount + increment > 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;
}
@ -171,7 +182,7 @@ public class QpackEncoder
_validateEncoding = validateEncoding;
}
public boolean referenceEntry(Entry entry, StreamInfo streamInfo)
public boolean referenceEntry(Entry entry)
{
if (entry == null)
return false;
@ -190,6 +201,14 @@ public class QpackEncoder
return true;
}
return false;
}
public boolean referenceEntry(Entry entry, StreamInfo streamInfo)
{
if (referenceEntry(entry))
return true;
// We may need to risk blocking the stream in order to reference it.
if (streamInfo.isBlocked())
{
@ -207,7 +226,7 @@ public class QpackEncoder
return false;
}
public static boolean shouldIndex(HttpField httpField)
public boolean shouldIndex(HttpField httpField)
{
return !DO_NOT_INDEX.contains(httpField.getHeader());
}
@ -349,6 +368,38 @@ public class QpackEncoder
}
}
public boolean insert(HttpField field)
{
DynamicTable dynamicTable = _context.getDynamicTable();
if (field.getValue() == null)
field = new HttpField(field.getHeader(), field.getName(), "");
boolean canCreateEntry = shouldIndex(field) && (Entry.getSize(field) <= dynamicTable.getSpace());
if (!canCreateEntry)
return false;
Entry newEntry = new Entry(field);
dynamicTable.add(newEntry);
Entry entry = _context.get(field);
if (referenceEntry(entry))
{
_handler.onInstruction(new DuplicateInstruction(entry.getIndex()));
return true;
}
boolean huffman = shouldHuffmanEncode(field);
Entry nameEntry = _context.get(field.getName());
if (referenceEntry(nameEntry))
{
_handler.onInstruction(new IndexedNameEntryInstruction(!nameEntry.isStatic(), nameEntry.getIndex(), huffman, field.getValue()));
return true;
}
_handler.onInstruction(new LiteralNameEntryInstruction(huffman, field.getName(), huffman, field.getValue()));
return true;
}
public static int encodeInsertCount(int reqInsertCount, int maxTableCapacity)
{
if (reqInsertCount == 0)

View File

@ -15,6 +15,7 @@ package org.eclipse.jetty.http3.qpack.table;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@ -22,7 +23,7 @@ import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http3.qpack.QpackContext;
import org.eclipse.jetty.http3.qpack.QpackException;
public class DynamicTable
public class DynamicTable implements Iterable<Entry>
{
public static final int FIRST_INDEX = StaticTable.STATIC_SIZE + 1;
private int _capacity;
@ -189,6 +190,12 @@ public class DynamicTable
return _capacity * 3 / 4;
}
@Override
public Iterator<Entry> iterator()
{
return _entries.iterator();
}
@Override
public String toString()
{

View File

@ -15,10 +15,12 @@ package org.eclipse.jetty.http3.qpack;
import java.nio.ByteBuffer;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http3.qpack.generator.IndexedNameEntryInstruction;
import org.eclipse.jetty.http3.qpack.generator.InsertCountIncrementInstruction;
import org.eclipse.jetty.http3.qpack.generator.Instruction;
import org.eclipse.jetty.http3.qpack.generator.LiteralNameEntryInstruction;
import org.eclipse.jetty.http3.qpack.generator.SectionAcknowledgmentInstruction;
import org.eclipse.jetty.http3.qpack.generator.SetCapacityInstruction;
import org.eclipse.jetty.http3.qpack.parser.DecoderInstructionParser;
@ -79,6 +81,8 @@ public class EncodeDecodeTest
assertThat(_decoderHandler.getInstruction(), instanceOf(SectionAcknowledgmentInstruction.class));
assertTrue(_decoderHandler.isEmpty());
_decoderInstructionParser.parse(toBuffer(new SectionAcknowledgmentInstruction(streamId)));
// B.2. Dynamic Table.
// Set capacity to 220.
@ -86,32 +90,33 @@ public class EncodeDecodeTest
Instruction instruction = _encoderHandler.getInstruction();
assertThat(instruction, instanceOf(SetCapacityInstruction.class));
assertThat(((SetCapacityInstruction)instruction).getCapacity(), is(220));
assertThat(BufferUtil.toHexString(toBuffer(instruction)), equalsHex("3fbd01"));
assertThat(toString(instruction), equalsHex("3fbd01"));
_encoderInstructionParser.parse(toHex("3fbd01"));
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.
streamId = 4;
httpFields = HttpFields.build()
.add(":authority", "www.example.com")
.add(":path", "/sample/path");
buffer = _encoder.encode(4, httpFields);
buffer = _encoder.encode(streamId, httpFields);
instruction = _encoderHandler.getInstruction();
assertThat(instruction, instanceOf(IndexedNameEntryInstruction.class));
assertThat(((IndexedNameEntryInstruction)instruction).getIndex(), is(0));
assertThat(((IndexedNameEntryInstruction)instruction).getValue(), is("www.example.com"));
assertThat(BufferUtil.toHexString(toBuffer(instruction)), equalsHex("c00f 7777 772e 6578 616d 706c 652e 636f 6d"));
assertThat(toString(instruction), equalsHex("c00f 7777 772e 6578 616d 706c 652e 636f 6d"));
instruction = _encoderHandler.getInstruction();
assertThat(instruction, instanceOf(IndexedNameEntryInstruction.class));
assertThat(((IndexedNameEntryInstruction)instruction).getIndex(), is(1));
assertThat(((IndexedNameEntryInstruction)instruction).getValue(), is("/sample/path"));
assertThat(BufferUtil.toHexString(toBuffer(instruction)), equalsHex("c10c 2f73 616d 706c 652f 7061 7468"));
assertThat(toString(instruction), equalsHex("c10c 2f73 616d 706c 652f 7061 7468"));
assertTrue(_encoderHandler.isEmpty());
// We cannot decode the buffer until we parse the two instructions generated above (we reach required insert count).
_decoder.decode(4, buffer);
_decoder.decode(streamId, buffer);
assertNull(_decoderHandler.getHttpFields());
_encoderInstructionParser.parse(toHex("c00f 7777 772e 6578 616d 706c 652e 636f 6d"));
@ -124,6 +129,17 @@ public class EncodeDecodeTest
assertThat(_decoderHandler.getInstruction(), instanceOf(SectionAcknowledgmentInstruction.class));
assertTrue(_decoderHandler.isEmpty());
// Parse the decoder instructions on the encoder.
_decoderInstructionParser.parse(toBuffer(new InsertCountIncrementInstruction(2)));
_decoderInstructionParser.parse(toBuffer(new SectionAcknowledgmentInstruction(streamId)));
// B.3. Speculative Insert
_encoder.insert(new HttpField("custom-key", "custom-value"));
instruction = _encoderHandler.getInstruction();
assertThat(instruction, instanceOf(LiteralNameEntryInstruction.class));
assertThat(toString(instruction), equalsHex("4a63 7573 746f 6d2d 6b65 790c 6375 7374 6f6d 2d76 616c 7565"));
_encoder.insertCountIncrement(1);
}
public static ByteBuffer toBuffer(Instruction instruction)
@ -134,6 +150,11 @@ public class EncodeDecodeTest
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+", "");