Implement insertNameWithReference and insertWithLiteralName in EncoderInstructionParser.
Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
This commit is contained in:
parent
b3cd2a1c27
commit
2916b60091
|
@ -53,11 +53,14 @@ public class DecoderInstructionParser
|
|||
|
||||
public void parse(ByteBuffer buffer)
|
||||
{
|
||||
if (buffer == null || !buffer.hasRemaining())
|
||||
return;
|
||||
|
||||
switch (_state)
|
||||
{
|
||||
case PARSING:
|
||||
// Get first byte without incrementing the buffers position.
|
||||
byte firstByte = buffer.slice().get();
|
||||
byte firstByte = buffer.get(buffer.position());
|
||||
if ((firstByte & 0x80) != 0)
|
||||
{
|
||||
_state = State.SECTION_ACKNOWLEDGEMENT;
|
||||
|
|
|
@ -21,8 +21,14 @@ import java.nio.ByteBuffer;
|
|||
public class EncoderInstructionParser
|
||||
{
|
||||
private final Handler _handler;
|
||||
private final NBitStringParser _stringParser;
|
||||
private final NBitIntegerParser _integerParser;
|
||||
private State _state = State.PARSING;
|
||||
private Operation _operation = Operation.NONE;
|
||||
|
||||
private boolean _referenceDynamicTable;
|
||||
private int _index;
|
||||
private String _name;
|
||||
|
||||
private enum State
|
||||
{
|
||||
|
@ -33,25 +39,41 @@ public class EncoderInstructionParser
|
|||
DUPLICATE
|
||||
}
|
||||
|
||||
private enum Operation
|
||||
{
|
||||
NONE,
|
||||
INDEX,
|
||||
NAME,
|
||||
VALUE,
|
||||
}
|
||||
|
||||
public interface Handler
|
||||
{
|
||||
void onSetDynamicTableCapacity(int capacity);
|
||||
|
||||
void onDuplicate(int index);
|
||||
|
||||
void onInsertNameWithReference(int nameIndex, boolean isDynamicTableIndex, String value);
|
||||
|
||||
void onInsertWithLiteralName(String name, String value);
|
||||
}
|
||||
|
||||
public EncoderInstructionParser(Handler handler)
|
||||
{
|
||||
_handler = handler;
|
||||
_stringParser = new NBitStringParser();
|
||||
_integerParser = new NBitIntegerParser();
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer buffer)
|
||||
public void parse(ByteBuffer buffer) throws QpackException
|
||||
{
|
||||
if (buffer == null || !buffer.hasRemaining())
|
||||
return;
|
||||
|
||||
switch (_state)
|
||||
{
|
||||
case PARSING:
|
||||
byte firstByte = buffer.slice().get();
|
||||
byte firstByte = buffer.get(buffer.position());
|
||||
if ((firstByte & 0x80) != 0)
|
||||
{
|
||||
_state = State.REFERENCED_NAME;
|
||||
|
@ -95,25 +117,76 @@ public class EncoderInstructionParser
|
|||
}
|
||||
}
|
||||
|
||||
private void parseInsertNameWithReference(ByteBuffer buffer)
|
||||
private void parseInsertNameWithReference(ByteBuffer buffer) throws QpackException
|
||||
{
|
||||
// TODO
|
||||
// Single bit boolean whether reference is to dynamic or static table.
|
||||
// Index with 6-bit prefix.
|
||||
// Single bit wither it is huffman encoded.
|
||||
// Length of the encoded string.
|
||||
// The string itself.
|
||||
while (true)
|
||||
{
|
||||
switch (_operation)
|
||||
{
|
||||
case NONE:
|
||||
byte firstByte = buffer.get(buffer.position());
|
||||
_referenceDynamicTable = (firstByte & 0x40) == 0;
|
||||
_operation = Operation.INDEX;
|
||||
continue;
|
||||
|
||||
case INDEX:
|
||||
_index = _integerParser.decode(buffer, 6);
|
||||
if (_index < 0)
|
||||
return;
|
||||
|
||||
_stringParser.setPrefix(8);
|
||||
_operation = Operation.VALUE;
|
||||
continue;
|
||||
|
||||
case VALUE:
|
||||
String value = _stringParser.decode(buffer);
|
||||
if (value == null)
|
||||
return;
|
||||
_operation = Operation.NONE;
|
||||
_state = State.PARSING;
|
||||
_handler.onInsertNameWithReference(_index, _referenceDynamicTable, value);
|
||||
return;
|
||||
|
||||
default:
|
||||
throw new IllegalStateException(_operation.name());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void parseInsertWithLiteralName(ByteBuffer buffer)
|
||||
private void parseInsertWithLiteralName(ByteBuffer buffer) throws QpackException
|
||||
{
|
||||
// TODO
|
||||
// Single bit whether name is huffman encoded.
|
||||
// Length of name with 5-bit prefix.
|
||||
// Name bytes.
|
||||
// Single bit whether value is huffman encoded.
|
||||
// Value length with 7-bit prefix.
|
||||
// Value bytes.
|
||||
while (true)
|
||||
{
|
||||
switch (_operation)
|
||||
{
|
||||
case NONE:
|
||||
_stringParser.setPrefix(6);
|
||||
_operation = Operation.NAME;
|
||||
continue;
|
||||
|
||||
case NAME:
|
||||
_name = _stringParser.decode(buffer);
|
||||
if (_name == null)
|
||||
return;
|
||||
|
||||
_stringParser.setPrefix(8);
|
||||
_operation = Operation.VALUE;
|
||||
continue;
|
||||
|
||||
case VALUE:
|
||||
String value = _stringParser.decode(buffer);
|
||||
if (value == null)
|
||||
return;
|
||||
|
||||
_operation = Operation.NONE;
|
||||
_state = State.PARSING;
|
||||
_handler.onInsertWithLiteralName(_name, value);
|
||||
return;
|
||||
|
||||
default:
|
||||
throw new IllegalStateException(_operation.name());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void parseDuplicate(ByteBuffer buffer)
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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.util.Utf8StringBuilder;
|
||||
|
||||
public class HuffmanDecoder
|
||||
{
|
||||
static final char EOS = Huffman.EOS;
|
||||
static final char[] tree = Huffman.tree;
|
||||
static final char[] rowsym = Huffman.rowsym;
|
||||
static final byte[] rowbits = Huffman.rowbits;
|
||||
|
||||
private final Utf8StringBuilder utf8 = new Utf8StringBuilder();
|
||||
private int count = 0;
|
||||
private int node = 0;
|
||||
private int current = 0;
|
||||
private int bits = 0;
|
||||
|
||||
public String decode(ByteBuffer buffer, int length) throws QpackException.CompressionException
|
||||
{
|
||||
for (; count < length; count++)
|
||||
{
|
||||
if (!buffer.hasRemaining())
|
||||
return null;
|
||||
|
||||
int b = buffer.get() & 0xFF;
|
||||
current = (current << 8) | b;
|
||||
bits += 8;
|
||||
while (bits >= 8)
|
||||
{
|
||||
int c = (current >>> (bits - 8)) & 0xFF;
|
||||
node = tree[node * 256 + c];
|
||||
if (rowbits[node] != 0)
|
||||
{
|
||||
if (rowsym[node] == EOS)
|
||||
{
|
||||
reset();
|
||||
throw new QpackException.CompressionException("EOS in content");
|
||||
}
|
||||
|
||||
// terminal node
|
||||
utf8.append((byte)(0xFF & rowsym[node]));
|
||||
bits -= rowbits[node];
|
||||
node = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// non-terminal node
|
||||
bits -= 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (bits > 0)
|
||||
{
|
||||
int c = (current << (8 - bits)) & 0xFF;
|
||||
int lastNode = node;
|
||||
node = tree[node * 256 + c];
|
||||
|
||||
if (rowbits[node] == 0 || rowbits[node] > bits)
|
||||
{
|
||||
int requiredPadding = 0;
|
||||
for (int i = 0; i < bits; i++)
|
||||
{
|
||||
requiredPadding = (requiredPadding << 1) | 1;
|
||||
}
|
||||
|
||||
if ((c >> (8 - bits)) != requiredPadding)
|
||||
throw new QpackException.CompressionException("Incorrect padding");
|
||||
|
||||
node = lastNode;
|
||||
break;
|
||||
}
|
||||
|
||||
utf8.append((byte)(0xFF & rowsym[node]));
|
||||
bits -= rowbits[node];
|
||||
node = 0;
|
||||
}
|
||||
|
||||
if (node != 0)
|
||||
throw new QpackException.CompressionException("Bad termination");
|
||||
|
||||
return utf8.toString();
|
||||
}
|
||||
|
||||
public void reset()
|
||||
{
|
||||
utf8.reset();
|
||||
count = 0;
|
||||
current = 0;
|
||||
node = 0;
|
||||
bits = 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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;
|
||||
|
||||
public class NBitStringParser
|
||||
{
|
||||
private final NBitIntegerParser _integerParser;
|
||||
private final HuffmanDecoder _huffmanBuilder;
|
||||
private final StringBuilder _stringBuilder;
|
||||
private boolean _huffman;
|
||||
private int _count;
|
||||
private int _length;
|
||||
private int _prefix;
|
||||
|
||||
private State _state = State.PARSING;
|
||||
|
||||
private enum State
|
||||
{
|
||||
PARSING,
|
||||
LENGTH,
|
||||
VALUE
|
||||
}
|
||||
|
||||
public NBitStringParser()
|
||||
{
|
||||
_integerParser = new NBitIntegerParser();
|
||||
_huffmanBuilder = new HuffmanDecoder();
|
||||
_stringBuilder = new StringBuilder();
|
||||
}
|
||||
|
||||
public void setPrefix(int prefix)
|
||||
{
|
||||
_prefix = prefix;
|
||||
}
|
||||
|
||||
public String decode(ByteBuffer buffer) throws QpackException
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
switch (_state)
|
||||
{
|
||||
case PARSING:
|
||||
byte firstByte = buffer.get(buffer.position());
|
||||
_huffman = ((0x80 >>> (8 - _prefix)) & firstByte) != 0;
|
||||
_state = State.LENGTH;
|
||||
continue;
|
||||
|
||||
case LENGTH:
|
||||
_length = _integerParser.decode(buffer, _prefix - 1);
|
||||
if (_length < 0)
|
||||
return null;
|
||||
_state = State.VALUE;
|
||||
continue;
|
||||
|
||||
case VALUE:
|
||||
String value = _huffman ? _huffmanBuilder.decode(buffer, _length) : asciiStringDecode(buffer);
|
||||
if (value != null)
|
||||
reset();
|
||||
return value;
|
||||
|
||||
default:
|
||||
throw new IllegalStateException(_state.name());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String asciiStringDecode(ByteBuffer buffer)
|
||||
{
|
||||
for (; _count < _length; _count++)
|
||||
{
|
||||
if (!buffer.hasRemaining())
|
||||
return null;
|
||||
_stringBuilder.append((char)(0x7F & buffer.get()));
|
||||
}
|
||||
return _stringBuilder.toString();
|
||||
}
|
||||
|
||||
public void reset()
|
||||
{
|
||||
_state = State.PARSING;
|
||||
_integerParser.reset();
|
||||
_huffmanBuilder.reset();
|
||||
_stringBuilder.setLength(0);
|
||||
_prefix = 0;
|
||||
_count = 0;
|
||||
_length = 0;
|
||||
_huffman = false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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.LinkedList;
|
||||
import java.util.Queue;
|
||||
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.TypeUtil;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class IncomingDecoderStreamTest
|
||||
{
|
||||
public static class DebugHandler implements EncoderInstructionParser.Handler
|
||||
{
|
||||
public Queue<Integer> setCapacities = new LinkedList<>();
|
||||
public Queue<Integer> duplicates = new LinkedList<>();
|
||||
public Queue<Entry> literalNameEntries = new LinkedList<>();
|
||||
public Queue<ReferencedEntry> referencedNameEntries = new LinkedList<>();
|
||||
|
||||
public static class Entry
|
||||
{
|
||||
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
|
||||
public void onSetDynamicTableCapacity(int capacity)
|
||||
{
|
||||
setCapacities.add(capacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDuplicate(int index)
|
||||
{
|
||||
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()
|
||||
{
|
||||
return setCapacities.isEmpty() && duplicates.isEmpty() && literalNameEntries.isEmpty() && referencedNameEntries.isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
private EncoderInstructionParser _instructionParser;
|
||||
private DebugHandler _handler;
|
||||
|
||||
@BeforeEach
|
||||
public void before()
|
||||
{
|
||||
_handler = new DebugHandler();
|
||||
_instructionParser = new EncoderInstructionParser(_handler);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddWithReferencedEntry() throws Exception
|
||||
{
|
||||
String insertAuthorityEntry = "c00f7777772e6578616d706c652e636f6d";
|
||||
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"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetCapacity() throws Exception
|
||||
{
|
||||
String setCapacityString = "3fbd01";
|
||||
ByteBuffer buffer = BufferUtil.toBuffer(TypeUtil.fromHexString(setCapacityString));
|
||||
_instructionParser.parse(buffer);
|
||||
assertThat(_handler.setCapacities.poll(), is(220));
|
||||
assertTrue(_handler.isEmpty());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue