Decouple the DynamicTable from the QpackContext.
Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
This commit is contained in:
parent
5d07842af8
commit
f39313b1d5
|
@ -17,6 +17,7 @@ import org.eclipse.jetty.http3.qpack.QpackFieldPreEncoder;
|
|||
module org.eclipse.jetty.http3.qpack
|
||||
{
|
||||
exports org.eclipse.jetty.http3.qpack;
|
||||
exports org.eclipse.jetty.http3.qpack.table;
|
||||
|
||||
requires transitive org.eclipse.jetty.http;
|
||||
requires org.slf4j;
|
||||
|
|
|
@ -15,6 +15,7 @@ package org.eclipse.jetty.http3.qpack;
|
|||
|
||||
import org.eclipse.jetty.http.HostPortHttpField;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http3.qpack.table.StaticTable;
|
||||
|
||||
/**
|
||||
*
|
||||
|
|
|
@ -13,13 +13,11 @@
|
|||
|
||||
package org.eclipse.jetty.http3.qpack;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http3.qpack.table.DynamicTable;
|
||||
import org.eclipse.jetty.http3.qpack.table.Entry;
|
||||
import org.eclipse.jetty.http3.qpack.table.StaticTable;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -36,35 +34,23 @@ public class QpackContext
|
|||
{
|
||||
public static final Logger LOG = LoggerFactory.getLogger(QpackContext.class);
|
||||
private static final StaticTable __staticTable = new StaticTable();
|
||||
|
||||
private final DynamicTable _dynamicTable;
|
||||
|
||||
private int _maxDynamicTableSizeInBytes;
|
||||
private int _dynamicTableSizeInBytes;
|
||||
|
||||
private final Map<HttpField, Entry> _fieldMap = new HashMap<>();
|
||||
private final Map<String, Entry> _nameMap = new HashMap<>();
|
||||
|
||||
QpackContext(int maxDynamicTableSize)
|
||||
{
|
||||
_maxDynamicTableSizeInBytes = maxDynamicTableSize;
|
||||
int guesstimateEntries = 10 + maxDynamicTableSize / (32 + 10 + 10);
|
||||
_dynamicTable = new DynamicTable(guesstimateEntries);
|
||||
_dynamicTable = new DynamicTable(maxDynamicTableSize);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug(String.format("HdrTbl[%x] created max=%d", hashCode(), maxDynamicTableSize));
|
||||
}
|
||||
|
||||
public void resize(int newMaxDynamicTableSize)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug(String.format("HdrTbl[%x] resized max=%d->%d", hashCode(), _maxDynamicTableSizeInBytes, newMaxDynamicTableSize));
|
||||
_maxDynamicTableSizeInBytes = newMaxDynamicTableSize;
|
||||
_dynamicTable.evict();
|
||||
_dynamicTable.setCapacity(newMaxDynamicTableSize);
|
||||
}
|
||||
|
||||
public Entry get(HttpField field)
|
||||
{
|
||||
Entry entry = _fieldMap.get(field);
|
||||
Entry entry = _dynamicTable.get(field);
|
||||
if (entry == null)
|
||||
entry = __staticTable.get(field);
|
||||
return entry;
|
||||
|
@ -75,7 +61,7 @@ public class QpackContext
|
|||
Entry entry = __staticTable.get(name);
|
||||
if (entry != null)
|
||||
return entry;
|
||||
return _nameMap.get(StringUtil.asciiToLowerCase(name));
|
||||
return _dynamicTable.get(StringUtil.asciiToLowerCase(name));
|
||||
}
|
||||
|
||||
public Entry get(int index)
|
||||
|
@ -96,32 +82,15 @@ public class QpackContext
|
|||
|
||||
public Entry add(HttpField field)
|
||||
{
|
||||
Entry entry = new Entry(field);
|
||||
int size = entry.getSize();
|
||||
if (size > _maxDynamicTableSizeInBytes)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug(String.format("HdrTbl[%x] !added size %d>%d", hashCode(), size, _maxDynamicTableSizeInBytes));
|
||||
_dynamicTable.evictAll();
|
||||
return null;
|
||||
}
|
||||
_dynamicTableSizeInBytes += size;
|
||||
_dynamicTable.add(entry);
|
||||
_fieldMap.put(field, entry);
|
||||
_nameMap.put(field.getLowerCaseName(), entry);
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug(String.format("HdrTbl[%x] added %s", hashCode(), entry));
|
||||
_dynamicTable.evict();
|
||||
return entry;
|
||||
return _dynamicTable.add(new Entry(field));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Current dynamic table size in entries
|
||||
*/
|
||||
public int size()
|
||||
public int getNumEntries()
|
||||
{
|
||||
return _dynamicTable.size();
|
||||
return _dynamicTable.getNumEntries();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -129,7 +98,7 @@ public class QpackContext
|
|||
*/
|
||||
public int getDynamicTableSize()
|
||||
{
|
||||
return _dynamicTableSizeInBytes;
|
||||
return _dynamicTable.getSize();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -137,19 +106,25 @@ public class QpackContext
|
|||
*/
|
||||
public int getMaxDynamicTableSize()
|
||||
{
|
||||
return _maxDynamicTableSizeInBytes;
|
||||
return _dynamicTable.getMaxSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return index of entry in COMBINED address space (QPACK has separate address spaces for dynamic and static tables).
|
||||
*/
|
||||
public int index(Entry entry)
|
||||
{
|
||||
if (entry._slot < 0)
|
||||
if (entry.getIndex() < 0)
|
||||
return 0;
|
||||
if (entry.isStatic())
|
||||
return entry._slot;
|
||||
return entry.getIndex();
|
||||
|
||||
return _dynamicTable.index(entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return index of entry in the static table or 0 if not in the table (I guess the entries start from 1 not 0 unlike QPACK).
|
||||
*/
|
||||
public static int staticIndex(HttpHeader header)
|
||||
{
|
||||
if (header == null)
|
||||
|
@ -157,194 +132,6 @@ public class QpackContext
|
|||
Entry entry = __staticTable.get(header.asString());
|
||||
if (entry == null)
|
||||
return 0;
|
||||
return entry._slot;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("QpackContext@%x{entries=%d,size=%d,max=%d}", hashCode(), _dynamicTable.size(), _dynamicTableSizeInBytes, _maxDynamicTableSizeInBytes);
|
||||
}
|
||||
|
||||
private class DynamicTable
|
||||
{
|
||||
Entry[] _entries;
|
||||
int _size;
|
||||
int _offset;
|
||||
int _growby;
|
||||
|
||||
private DynamicTable(int initCapacity)
|
||||
{
|
||||
_entries = new Entry[initCapacity];
|
||||
_growby = initCapacity;
|
||||
}
|
||||
|
||||
public void add(Entry entry)
|
||||
{
|
||||
if (_size == _entries.length)
|
||||
{
|
||||
Entry[] entries = new Entry[_entries.length + _growby];
|
||||
for (int i = 0; i < _size; i++)
|
||||
{
|
||||
int slot = (_offset + i) % _entries.length;
|
||||
entries[i] = _entries[slot];
|
||||
entries[i]._slot = i;
|
||||
}
|
||||
_entries = entries;
|
||||
_offset = 0;
|
||||
}
|
||||
int slot = (_size++ + _offset) % _entries.length;
|
||||
_entries[slot] = entry;
|
||||
entry._slot = slot;
|
||||
}
|
||||
|
||||
public int index(Entry entry)
|
||||
{
|
||||
return StaticTable.STATIC_SIZE + _size - (entry._slot - _offset + _entries.length) % _entries.length;
|
||||
}
|
||||
|
||||
public Entry get(int index)
|
||||
{
|
||||
int d = index - StaticTable.STATIC_SIZE - 1;
|
||||
if (d < 0 || d >= _size)
|
||||
return null;
|
||||
int slot = (_offset + _size - d - 1) % _entries.length;
|
||||
return _entries[slot];
|
||||
}
|
||||
|
||||
public int size()
|
||||
{
|
||||
return _size;
|
||||
}
|
||||
|
||||
private void evict()
|
||||
{
|
||||
while (_dynamicTableSizeInBytes > _maxDynamicTableSizeInBytes)
|
||||
{
|
||||
Entry entry = _entries[_offset];
|
||||
_entries[_offset] = null;
|
||||
_offset = (_offset + 1) % _entries.length;
|
||||
_size--;
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug(String.format("HdrTbl[%x] evict %s", QpackContext.this.hashCode(), entry));
|
||||
_dynamicTableSizeInBytes -= entry.getSize();
|
||||
entry._slot = -1;
|
||||
_fieldMap.remove(entry.getHttpField());
|
||||
String lc = entry.getHttpField().getLowerCaseName();
|
||||
if (entry == _nameMap.get(lc))
|
||||
_nameMap.remove(lc);
|
||||
}
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug(String.format("HdrTbl[%x] entries=%d, size=%d, max=%d", QpackContext.this.hashCode(), _dynamicTable.size(), _dynamicTableSizeInBytes, _maxDynamicTableSizeInBytes));
|
||||
}
|
||||
|
||||
private void evictAll()
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug(String.format("HdrTbl[%x] evictAll", QpackContext.this.hashCode()));
|
||||
if (size() > 0)
|
||||
{
|
||||
_fieldMap.clear();
|
||||
_nameMap.clear();
|
||||
_offset = 0;
|
||||
_size = 0;
|
||||
_dynamicTableSizeInBytes = 0;
|
||||
Arrays.fill(_entries, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class Entry
|
||||
{
|
||||
final HttpField _field;
|
||||
int _slot; // The index within it's array
|
||||
|
||||
Entry()
|
||||
{
|
||||
_slot = -1;
|
||||
_field = null;
|
||||
}
|
||||
|
||||
Entry(HttpField field)
|
||||
{
|
||||
_field = field;
|
||||
}
|
||||
|
||||
public int getSize()
|
||||
{
|
||||
String value = _field.getValue();
|
||||
return 32 + _field.getName().length() + (value == null ? 0 : value.length());
|
||||
}
|
||||
|
||||
public HttpField getHttpField()
|
||||
{
|
||||
return _field;
|
||||
}
|
||||
|
||||
public boolean isStatic()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public byte[] getStaticHuffmanValue()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("{%s,%d,%s,%x}", isStatic() ? "S" : "D", _slot, _field, hashCode());
|
||||
}
|
||||
}
|
||||
|
||||
public static class StaticEntry extends Entry
|
||||
{
|
||||
private final byte[] _huffmanValue;
|
||||
private final byte _encodedField;
|
||||
|
||||
StaticEntry(int index, HttpField field)
|
||||
{
|
||||
super(field);
|
||||
_slot = index;
|
||||
String value = field.getValue();
|
||||
if (value != null && value.length() > 0)
|
||||
{
|
||||
int huffmanLen = Huffman.octetsNeeded(value);
|
||||
if (huffmanLen < 0)
|
||||
throw new IllegalStateException("bad value");
|
||||
int lenLen = NBitInteger.octectsNeeded(7, huffmanLen);
|
||||
_huffmanValue = new byte[1 + lenLen + huffmanLen];
|
||||
ByteBuffer buffer = ByteBuffer.wrap(_huffmanValue);
|
||||
|
||||
// Indicate Huffman
|
||||
buffer.put((byte)0x80);
|
||||
// Add huffman length
|
||||
NBitInteger.encode(buffer, 7, huffmanLen);
|
||||
// Encode value
|
||||
Huffman.encode(buffer, value);
|
||||
}
|
||||
else
|
||||
_huffmanValue = null;
|
||||
|
||||
_encodedField = (byte)(0x80 | index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStatic()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getStaticHuffmanValue()
|
||||
{
|
||||
return _huffmanValue;
|
||||
}
|
||||
|
||||
public byte getEncodedField()
|
||||
{
|
||||
return _encodedField;
|
||||
}
|
||||
return entry.getIndex();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ import org.eclipse.jetty.http.HttpField;
|
|||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpTokens;
|
||||
import org.eclipse.jetty.http.MetaData;
|
||||
import org.eclipse.jetty.http3.qpack.QpackContext.Entry;
|
||||
import org.eclipse.jetty.http3.qpack.table.Entry;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
|
|
@ -29,8 +29,8 @@ import org.eclipse.jetty.http.HttpStatus;
|
|||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.http.MetaData;
|
||||
import org.eclipse.jetty.http.PreEncodedHttpField;
|
||||
import org.eclipse.jetty.http3.qpack.QpackContext.Entry;
|
||||
import org.eclipse.jetty.http3.qpack.QpackContext.StaticEntry;
|
||||
import org.eclipse.jetty.http3.qpack.table.Entry;
|
||||
import org.eclipse.jetty.http3.qpack.table.StaticEntry;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.slf4j.Logger;
|
||||
|
|
|
@ -0,0 +1,157 @@
|
|||
package org.eclipse.jetty.http3.qpack.table;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http3.qpack.QpackContext;
|
||||
|
||||
public class DynamicTable
|
||||
{
|
||||
public static final int FIRST_INDEX = StaticTable.STATIC_SIZE + 1;
|
||||
private int _maxDynamicTableSizeInBytes;
|
||||
private int _dynamicTableSizeInBytes;
|
||||
|
||||
private final Map<HttpField, Entry> _fieldMap = new HashMap<>();
|
||||
private final Map<String, Entry> _nameMap = new HashMap<>();
|
||||
private final int _growby;
|
||||
|
||||
private Entry[] _entries;
|
||||
private int _numEntries;
|
||||
private int _offset;
|
||||
|
||||
public DynamicTable(int maxSize)
|
||||
{
|
||||
_maxDynamicTableSizeInBytes = maxSize;
|
||||
int initCapacity = 10 + maxSize / (32 + 10 + 10);
|
||||
_entries = new Entry[initCapacity];
|
||||
_growby = initCapacity;
|
||||
}
|
||||
|
||||
public Entry add(Entry entry)
|
||||
{
|
||||
int size = entry.getSize();
|
||||
if (size > _maxDynamicTableSizeInBytes)
|
||||
{
|
||||
if (QpackContext.LOG.isDebugEnabled())
|
||||
QpackContext.LOG.debug(String.format("HdrTbl[%x] !added size %d>%d", hashCode(), size, _maxDynamicTableSizeInBytes));
|
||||
evictAll();
|
||||
return null;
|
||||
}
|
||||
_dynamicTableSizeInBytes += size;
|
||||
|
||||
if (_numEntries == _entries.length)
|
||||
{
|
||||
Entry[] entries = new Entry[_entries.length + _growby];
|
||||
for (int i = 0; i < _numEntries; i++)
|
||||
{
|
||||
int slot = (_offset + i) % _entries.length;
|
||||
entries[i] = _entries[slot];
|
||||
entries[i].setIndex(i);
|
||||
}
|
||||
_entries = entries;
|
||||
_offset = 0;
|
||||
}
|
||||
int slot = (_numEntries++ + _offset) % _entries.length;
|
||||
_entries[slot] = entry;
|
||||
entry.setIndex(slot);
|
||||
|
||||
_fieldMap.put(entry.getHttpField(), entry);
|
||||
_nameMap.put(entry.getHttpField().getLowerCaseName(), entry);
|
||||
|
||||
if (QpackContext.LOG.isDebugEnabled())
|
||||
QpackContext.LOG.debug(String.format("HdrTbl[%x] added %s", hashCode(), entry));
|
||||
evict();
|
||||
return entry;
|
||||
}
|
||||
|
||||
public int index(Entry entry)
|
||||
{
|
||||
return StaticTable.STATIC_SIZE + _numEntries - (entry.getIndex() - _offset + _entries.length) % _entries.length;
|
||||
}
|
||||
|
||||
public Entry get(int index)
|
||||
{
|
||||
int d = index - StaticTable.STATIC_SIZE - 1;
|
||||
if (d < 0 || d >= _numEntries)
|
||||
return null;
|
||||
int slot = (_offset + _numEntries - d - 1) % _entries.length;
|
||||
return _entries[slot];
|
||||
}
|
||||
|
||||
public Entry get(String name)
|
||||
{
|
||||
return _nameMap.get(name);
|
||||
}
|
||||
|
||||
public Entry get(HttpField field)
|
||||
{
|
||||
return _fieldMap.get(field);
|
||||
}
|
||||
|
||||
public int getSize()
|
||||
{
|
||||
return _dynamicTableSizeInBytes;
|
||||
}
|
||||
|
||||
public int getMaxSize()
|
||||
{
|
||||
return _maxDynamicTableSizeInBytes;
|
||||
}
|
||||
|
||||
public int getNumEntries()
|
||||
{
|
||||
return _numEntries;
|
||||
}
|
||||
|
||||
public void setCapacity(int capacity)
|
||||
{
|
||||
if (QpackContext.LOG.isDebugEnabled())
|
||||
QpackContext.LOG.debug(String.format("HdrTbl[%x] resized max=%d->%d", hashCode(), _maxDynamicTableSizeInBytes, capacity));
|
||||
_maxDynamicTableSizeInBytes = capacity;
|
||||
evict();
|
||||
}
|
||||
|
||||
private void evict()
|
||||
{
|
||||
while (_dynamicTableSizeInBytes > _maxDynamicTableSizeInBytes)
|
||||
{
|
||||
Entry entry = _entries[_offset];
|
||||
_entries[_offset] = null;
|
||||
_offset = (_offset + 1) % _entries.length;
|
||||
_numEntries--;
|
||||
if (QpackContext.LOG.isDebugEnabled())
|
||||
QpackContext.LOG.debug(String.format("HdrTbl[%x] evict %s", hashCode(), entry));
|
||||
_dynamicTableSizeInBytes -= entry.getSize();
|
||||
entry.setIndex(-1);
|
||||
_fieldMap.remove(entry.getHttpField());
|
||||
String lc = entry.getHttpField().getLowerCaseName();
|
||||
if (entry == _nameMap.get(lc))
|
||||
_nameMap.remove(lc);
|
||||
}
|
||||
if (QpackContext.LOG.isDebugEnabled())
|
||||
QpackContext.LOG.debug(String.format("HdrTbl[%x] entries=%d, size=%d, max=%d", hashCode(), getNumEntries(), _dynamicTableSizeInBytes, _maxDynamicTableSizeInBytes));
|
||||
}
|
||||
|
||||
private void evictAll()
|
||||
{
|
||||
if (QpackContext.LOG.isDebugEnabled())
|
||||
QpackContext.LOG.debug(String.format("HdrTbl[%x] evictAll", hashCode()));
|
||||
if (getNumEntries() > 0)
|
||||
{
|
||||
_fieldMap.clear();
|
||||
_nameMap.clear();
|
||||
_offset = 0;
|
||||
_numEntries = 0;
|
||||
_dynamicTableSizeInBytes = 0;
|
||||
Arrays.fill(_entries, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("%s@%x{entries=%d,size=%d,max=%d}", getClass().getSimpleName(), hashCode(), getNumEntries(), _dynamicTableSizeInBytes, _maxDynamicTableSizeInBytes);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
package org.eclipse.jetty.http3.qpack.table;
|
||||
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
|
||||
public class Entry
|
||||
{
|
||||
final HttpField _field;
|
||||
private int _slot; // The index within it's array
|
||||
|
||||
public Entry()
|
||||
{
|
||||
this(-1, null);
|
||||
}
|
||||
|
||||
public Entry(HttpField field)
|
||||
{
|
||||
this(-1, field);
|
||||
}
|
||||
|
||||
public Entry(int index, HttpField field)
|
||||
{
|
||||
_field = field;
|
||||
_slot = index;
|
||||
}
|
||||
|
||||
public int getSize()
|
||||
{
|
||||
String value = _field.getValue();
|
||||
return 32 + _field.getName().length() + (value == null ? 0 : value.length());
|
||||
}
|
||||
|
||||
public void setIndex(int index)
|
||||
{
|
||||
_slot = index;
|
||||
}
|
||||
|
||||
public int getIndex()
|
||||
{
|
||||
return _slot;
|
||||
}
|
||||
|
||||
public HttpField getHttpField()
|
||||
{
|
||||
return _field;
|
||||
}
|
||||
|
||||
public boolean isStatic()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public byte[] getStaticHuffmanValue()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("{%s,%d,%s,%x}", isStatic() ? "S" : "D", _slot, _field, hashCode());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package org.eclipse.jetty.http3.qpack.table;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http3.qpack.Huffman;
|
||||
import org.eclipse.jetty.http3.qpack.NBitInteger;
|
||||
|
||||
public class StaticEntry extends Entry
|
||||
{
|
||||
private final byte[] _huffmanValue;
|
||||
private final byte _encodedField;
|
||||
|
||||
StaticEntry(int index, HttpField field)
|
||||
{
|
||||
super(index, field);
|
||||
String value = field.getValue();
|
||||
if (value != null && value.length() > 0)
|
||||
{
|
||||
int huffmanLen = Huffman.octetsNeeded(value);
|
||||
if (huffmanLen < 0)
|
||||
throw new IllegalStateException("bad value");
|
||||
int lenLen = NBitInteger.octectsNeeded(7, huffmanLen);
|
||||
_huffmanValue = new byte[1 + lenLen + huffmanLen];
|
||||
ByteBuffer buffer = ByteBuffer.wrap(_huffmanValue);
|
||||
|
||||
// Indicate Huffman
|
||||
buffer.put((byte)0x80);
|
||||
// Add huffman length
|
||||
NBitInteger.encode(buffer, 7, huffmanLen);
|
||||
// Encode value
|
||||
Huffman.encode(buffer, value);
|
||||
}
|
||||
else
|
||||
_huffmanValue = null;
|
||||
|
||||
_encodedField = (byte)(0x80 | index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStatic()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getStaticHuffmanValue()
|
||||
{
|
||||
return _huffmanValue;
|
||||
}
|
||||
|
||||
public byte getEncodedField()
|
||||
{
|
||||
return _encodedField;
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package org.eclipse.jetty.http3.qpack;
|
||||
package org.eclipse.jetty.http3.qpack.table;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
|
@ -9,6 +9,7 @@ import org.eclipse.jetty.http.HttpField;
|
|||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpScheme;
|
||||
import org.eclipse.jetty.http3.qpack.StaticTableHttpField;
|
||||
import org.eclipse.jetty.util.Index;
|
||||
|
||||
public class StaticTable
|
||||
|
@ -82,18 +83,18 @@ public class StaticTable
|
|||
|
||||
public static final int STATIC_SIZE = STATIC_TABLE.length - 1;
|
||||
|
||||
private final Map<HttpField, QpackContext.Entry> __staticFieldMap = new HashMap<>();
|
||||
private final Index<QpackContext.StaticEntry> __staticNameMap;
|
||||
private final QpackContext.StaticEntry[] __staticTableByHeader = new QpackContext.StaticEntry[HttpHeader.values().length];
|
||||
private final QpackContext.StaticEntry[] __staticTable = new QpackContext.StaticEntry[STATIC_TABLE.length];
|
||||
private final Map<HttpField, Entry> __staticFieldMap = new HashMap<>();
|
||||
private final Index<StaticEntry> __staticNameMap;
|
||||
private final StaticEntry[] __staticTableByHeader = new StaticEntry[HttpHeader.values().length];
|
||||
private final StaticEntry[] __staticTable = new StaticEntry[STATIC_TABLE.length];
|
||||
|
||||
public StaticTable()
|
||||
{
|
||||
Index.Builder<QpackContext.StaticEntry> staticNameMapBuilder = new Index.Builder<QpackContext.StaticEntry>().caseSensitive(false);
|
||||
Index.Builder<StaticEntry> staticNameMapBuilder = new Index.Builder<StaticEntry>().caseSensitive(false);
|
||||
Set<String> added = new HashSet<>();
|
||||
for (int i = 1; i < STATIC_TABLE.length; i++)
|
||||
{
|
||||
QpackContext.StaticEntry entry = null;
|
||||
StaticEntry entry = null;
|
||||
|
||||
String name = STATIC_TABLE[i][0];
|
||||
String value = STATIC_TABLE[i][1];
|
||||
|
@ -107,7 +108,7 @@ public class StaticTable
|
|||
|
||||
HttpMethod method = HttpMethod.CACHE.get(value);
|
||||
if (method != null)
|
||||
entry = new QpackContext.StaticEntry(i, new StaticTableHttpField(header, name, value, method));
|
||||
entry = new StaticEntry(i, new StaticTableHttpField(header, name, value, method));
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -116,13 +117,13 @@ public class StaticTable
|
|||
|
||||
HttpScheme scheme = HttpScheme.CACHE.get(value);
|
||||
if (scheme != null)
|
||||
entry = new QpackContext.StaticEntry(i, new StaticTableHttpField(header, name, value, scheme));
|
||||
entry = new StaticEntry(i, new StaticTableHttpField(header, name, value, scheme));
|
||||
break;
|
||||
}
|
||||
|
||||
case C_STATUS:
|
||||
{
|
||||
entry = new QpackContext.StaticEntry(i, new StaticTableHttpField(header, name, value, value));
|
||||
entry = new StaticEntry(i, new StaticTableHttpField(header, name, value, value));
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -132,7 +133,7 @@ public class StaticTable
|
|||
}
|
||||
|
||||
if (entry == null)
|
||||
entry = new QpackContext.StaticEntry(i, header == null ? new HttpField(STATIC_TABLE[i][0], value) : new HttpField(header, name, value));
|
||||
entry = new StaticEntry(i, header == null ? new HttpField(STATIC_TABLE[i][0], value) : new HttpField(header, name, value));
|
||||
|
||||
__staticTable[i] = entry;
|
||||
|
||||
|
@ -149,30 +150,30 @@ public class StaticTable
|
|||
|
||||
for (HttpHeader h : HttpHeader.values())
|
||||
{
|
||||
QpackContext.StaticEntry entry = __staticNameMap.get(h.asString());
|
||||
StaticEntry entry = __staticNameMap.get(h.asString());
|
||||
if (entry != null)
|
||||
__staticTableByHeader[h.ordinal()] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
public QpackContext.Entry get(HttpField field)
|
||||
public Entry get(HttpField field)
|
||||
{
|
||||
return __staticFieldMap.get(field);
|
||||
}
|
||||
|
||||
public QpackContext.Entry get(String name)
|
||||
public Entry get(String name)
|
||||
{
|
||||
return __staticNameMap.get(name);
|
||||
}
|
||||
|
||||
public QpackContext.Entry get(int index)
|
||||
public Entry get(int index)
|
||||
{
|
||||
if (index >= __staticTable.length)
|
||||
return null;
|
||||
return __staticTable[index];
|
||||
}
|
||||
|
||||
public QpackContext.Entry get(HttpHeader header)
|
||||
public Entry get(HttpHeader header)
|
||||
{
|
||||
int index = header.ordinal();
|
||||
if (index >= __staticTableByHeader.length)
|
|
@ -16,7 +16,7 @@ package org.eclipse.jetty.http3.qpack;
|
|||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http3.qpack.QpackContext.Entry;
|
||||
import org.eclipse.jetty.http3.qpack.table.Entry;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
@ -197,7 +197,7 @@ public class QpackContextTest
|
|||
Entry[] entry = new Entry[100];
|
||||
|
||||
// Lookup the index of a static field
|
||||
assertEquals(0, ctx.size());
|
||||
assertEquals(0, ctx.getNumEntries());
|
||||
assertEquals(":authority", ctx.get(1).getHttpField().getName());
|
||||
assertEquals(3, ctx.index(ctx.get(methodPost)));
|
||||
assertEquals(methodPost, ctx.get(3).getHttpField());
|
||||
|
@ -208,7 +208,7 @@ public class QpackContextTest
|
|||
entry[0] = ctx.add(field[0]);
|
||||
|
||||
// Check new entry is 62
|
||||
assertEquals(1, ctx.size());
|
||||
assertEquals(1, ctx.getNumEntries());
|
||||
assertEquals(62, ctx.index(entry[0]));
|
||||
assertEquals(entry[0], ctx.get(62));
|
||||
|
||||
|
@ -217,7 +217,7 @@ public class QpackContextTest
|
|||
assertEquals(3, ctx.index(ctx.get(methodPost)));
|
||||
assertEquals(methodPost, ctx.get(3).getHttpField());
|
||||
assertEquals("www-authenticate", ctx.get(61).getHttpField().getName());
|
||||
assertEquals(null, ctx.get(62 + ctx.size()));
|
||||
assertEquals(null, ctx.get(62 + ctx.getNumEntries()));
|
||||
|
||||
// Add 4 more entries
|
||||
for (int i = 1; i <= 4; i++)
|
||||
|
@ -226,7 +226,7 @@ public class QpackContextTest
|
|||
}
|
||||
|
||||
// Check newest entry is at 62 oldest at 66
|
||||
assertEquals(5, ctx.size());
|
||||
assertEquals(5, ctx.getNumEntries());
|
||||
int index = 66;
|
||||
for (int i = 0; i <= 4; i++)
|
||||
{
|
||||
|
@ -240,7 +240,7 @@ public class QpackContextTest
|
|||
assertEquals(3, ctx.index(ctx.get(methodPost)));
|
||||
assertEquals(methodPost, ctx.get(3).getHttpField());
|
||||
assertEquals("www-authenticate", ctx.get(61).getHttpField().getName());
|
||||
assertEquals(null, ctx.get(62 + ctx.size()));
|
||||
assertEquals(null, ctx.get(62 + ctx.getNumEntries()));
|
||||
|
||||
// add 1 more entry and this should cause an eviction!
|
||||
entry[5] = ctx.add(field[5]);
|
||||
|
@ -262,7 +262,7 @@ public class QpackContextTest
|
|||
assertEquals(3, ctx.index(ctx.get(methodPost)));
|
||||
assertEquals(methodPost, ctx.get(3).getHttpField());
|
||||
assertEquals("www-authenticate", ctx.get(61).getHttpField().getName());
|
||||
assertEquals(null, ctx.get(62 + ctx.size()));
|
||||
assertEquals(null, ctx.get(62 + ctx.getNumEntries()));
|
||||
|
||||
// Add 4 more entries
|
||||
for (int i = 6; i <= 9; i++)
|
||||
|
@ -328,7 +328,7 @@ public class QpackContextTest
|
|||
entry[i] = ctx.add(field[i]);
|
||||
}
|
||||
|
||||
assertEquals(5, ctx.size());
|
||||
assertEquals(5, ctx.getNumEntries());
|
||||
|
||||
// check indexes
|
||||
int index = 66;
|
||||
|
@ -341,7 +341,7 @@ public class QpackContextTest
|
|||
|
||||
// resize so that only 2 entries may be held
|
||||
ctx.resize(38 * 2);
|
||||
assertEquals(2, ctx.size());
|
||||
assertEquals(2, ctx.getNumEntries());
|
||||
|
||||
// check indexes
|
||||
index = 63;
|
||||
|
@ -354,7 +354,7 @@ public class QpackContextTest
|
|||
|
||||
// resize so that 6.5 entries may be held
|
||||
ctx.resize(38 * 6 + 19);
|
||||
assertEquals(2, ctx.size());
|
||||
assertEquals(2, ctx.getNumEntries());
|
||||
|
||||
// check indexes
|
||||
index = 63;
|
||||
|
@ -371,7 +371,7 @@ public class QpackContextTest
|
|||
entry[i] = ctx.add(field[i]);
|
||||
}
|
||||
|
||||
assertEquals(6, ctx.size());
|
||||
assertEquals(6, ctx.getNumEntries());
|
||||
|
||||
// check indexes
|
||||
index = 67;
|
||||
|
@ -384,7 +384,7 @@ public class QpackContextTest
|
|||
|
||||
// resize so that only 100 entries may be held
|
||||
ctx.resize(38 * 100);
|
||||
assertEquals(6, ctx.size());
|
||||
assertEquals(6, ctx.getNumEntries());
|
||||
// check indexes
|
||||
index = 67;
|
||||
for (int i = 4; i <= 9; i++)
|
||||
|
|
|
@ -209,7 +209,7 @@ public class QpackDecoderTest
|
|||
assertThat(metaData.getFields().get(HttpHeader.HOST), is("localhost0"));
|
||||
assertThat(metaData.getFields().get(HttpHeader.COOKIE), is("abcdefghij"));
|
||||
assertThat(decoder.getQpackContext().getMaxDynamicTableSize(), is(50));
|
||||
assertThat(decoder.getQpackContext().size(), is(1));
|
||||
assertThat(decoder.getQpackContext().getNumEntries(), is(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -20,6 +20,7 @@ import org.eclipse.jetty.http.HttpFields;
|
|||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.http.MetaData;
|
||||
import org.eclipse.jetty.http3.qpack.table.StaticTable;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -67,7 +68,7 @@ public class QpackEncoderTest
|
|||
assertThat(buffer.remaining(), Matchers.greaterThan(0));
|
||||
|
||||
// All are in the dynamic table
|
||||
assertEquals(4, encoder.getQpackContext().size());
|
||||
assertEquals(4, encoder.getQpackContext().getNumEntries());
|
||||
|
||||
// encode exact same fields again!
|
||||
BufferUtil.clearToFill(buffer);
|
||||
|
@ -75,7 +76,7 @@ public class QpackEncoderTest
|
|||
BufferUtil.flipToFlush(buffer, 0);
|
||||
|
||||
// All are in the dynamic table
|
||||
assertEquals(4, encoder.getQpackContext().size());
|
||||
assertEquals(4, encoder.getQpackContext().getNumEntries());
|
||||
|
||||
// Add 4 more fields
|
||||
for (int i = 4; i <= 7; i++)
|
||||
|
@ -92,7 +93,7 @@ public class QpackEncoderTest
|
|||
assertThat(buffer.remaining(), Matchers.greaterThan(0));
|
||||
|
||||
// max dynamic table size reached
|
||||
assertEquals(5, encoder.getQpackContext().size());
|
||||
assertEquals(5, encoder.getQpackContext().getNumEntries());
|
||||
|
||||
// remove some fields
|
||||
for (int i = 0; i <= 7; i += 2)
|
||||
|
@ -109,7 +110,7 @@ public class QpackEncoderTest
|
|||
assertThat(buffer.remaining(), Matchers.greaterThan(0));
|
||||
|
||||
// max dynamic table size reached
|
||||
assertEquals(5, encoder.getQpackContext().size());
|
||||
assertEquals(5, encoder.getQpackContext().getNumEntries());
|
||||
|
||||
// remove another fields
|
||||
fields.remove(field[1].getName());
|
||||
|
@ -123,7 +124,7 @@ public class QpackEncoderTest
|
|||
assertThat(buffer.remaining(), Matchers.greaterThan(0));
|
||||
|
||||
// max dynamic table size reached
|
||||
assertEquals(5, encoder.getQpackContext().size());
|
||||
assertEquals(5, encoder.getQpackContext().getNumEntries());
|
||||
|
||||
// re add the field
|
||||
|
||||
|
@ -138,7 +139,7 @@ public class QpackEncoderTest
|
|||
assertThat(buffer.remaining(), Matchers.greaterThan(0));
|
||||
|
||||
// max dynamic table size reached
|
||||
assertEquals(5, encoder.getQpackContext().size());
|
||||
assertEquals(5, encoder.getQpackContext().getNumEntries());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -207,7 +208,7 @@ public class QpackEncoderTest
|
|||
assertThat(buffer.remaining(), Matchers.greaterThan(0));
|
||||
|
||||
// empty dynamic table
|
||||
assertEquals(0, encoder.getQpackContext().size());
|
||||
assertEquals(0, encoder.getQpackContext().getNumEntries());
|
||||
|
||||
// encode again
|
||||
BufferUtil.clearToFill(buffer);
|
||||
|
@ -218,7 +219,7 @@ public class QpackEncoderTest
|
|||
assertThat(buffer.remaining(), Matchers.greaterThan(0));
|
||||
|
||||
// empty dynamic table
|
||||
assertEquals(0, encoder.getQpackContext().size());
|
||||
assertEquals(0, encoder.getQpackContext().getNumEntries());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -278,7 +279,7 @@ public class QpackEncoderTest
|
|||
|
||||
// Only first and third fields are put in the table
|
||||
QpackContext context = encoder.getQpackContext();
|
||||
assertThat(context.size(), equalTo(2));
|
||||
assertThat(context.getNumEntries(), equalTo(2));
|
||||
assertThat(context.get(StaticTable.STATIC_SIZE + 1).getHttpField().getName(), equalTo("host"));
|
||||
assertThat(context.get(StaticTable.STATIC_SIZE + 2).getHttpField().getName(), equalTo("user-agent"));
|
||||
assertThat(context.getDynamicTableSize(), equalTo(
|
||||
|
@ -304,6 +305,6 @@ public class QpackEncoderTest
|
|||
QpackContext context = encoder.getQpackContext();
|
||||
|
||||
assertThat(context.getMaxDynamicTableSize(), Matchers.is(50));
|
||||
assertThat(context.size(), Matchers.is(1));
|
||||
assertThat(context.getNumEntries(), Matchers.is(1));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import org.eclipse.jetty.http.HttpVersion;
|
|||
import org.eclipse.jetty.http.MetaData;
|
||||
import org.eclipse.jetty.http.MetaData.Response;
|
||||
import org.eclipse.jetty.http.PreEncodedHttpField;
|
||||
import org.eclipse.jetty.http3.qpack.table.DynamicTable;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
@ -174,10 +175,10 @@ public class QpackTest
|
|||
BufferUtil.flipToFlush(buffer, 0);
|
||||
MetaData decoded0 = decoder.decode(buffer);
|
||||
|
||||
assertEquals(2, encoder.getQpackContext().size());
|
||||
assertEquals(2, decoder.getQpackContext().size());
|
||||
assertEquals(longEnoughToBeEvicted, encoder.getQpackContext().get(StaticTable.STATIC_TABLE.length + 1).getHttpField().getName());
|
||||
assertEquals("foo", encoder.getQpackContext().get(StaticTable.STATIC_TABLE.length).getHttpField().getName());
|
||||
assertEquals(2, encoder.getQpackContext().getNumEntries());
|
||||
assertEquals(2, decoder.getQpackContext().getNumEntries());
|
||||
assertEquals(longEnoughToBeEvicted, encoder.getQpackContext().get(DynamicTable.FIRST_INDEX + 1).getHttpField().getName());
|
||||
assertEquals("foo", encoder.getQpackContext().get(DynamicTable.FIRST_INDEX).getHttpField().getName());
|
||||
|
||||
assertMetaDataSame(original0, decoded0);
|
||||
|
||||
|
@ -192,10 +193,10 @@ public class QpackTest
|
|||
MetaData decoded1 = decoder.decode(buffer);
|
||||
assertMetaDataSame(original1, decoded1);
|
||||
|
||||
assertEquals(2, encoder.getQpackContext().size());
|
||||
assertEquals(2, decoder.getQpackContext().size());
|
||||
assertEquals("x", encoder.getQpackContext().get(StaticTable.STATIC_TABLE.length).getHttpField().getName());
|
||||
assertEquals("foo", encoder.getQpackContext().get(StaticTable.STATIC_TABLE.length + 1).getHttpField().getName());
|
||||
assertEquals(2, encoder.getQpackContext().getNumEntries());
|
||||
assertEquals(2, decoder.getQpackContext().getNumEntries());
|
||||
assertEquals("x", encoder.getQpackContext().get(DynamicTable.FIRST_INDEX).getHttpField().getName());
|
||||
assertEquals("foo", encoder.getQpackContext().get(DynamicTable.FIRST_INDEX + 1).getHttpField().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
Loading…
Reference in New Issue