Use classes for encoder and decoder instructions.

Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
This commit is contained in:
Lachlan Roberts 2021-02-13 01:53:17 +11:00 committed by Simone Bordet
parent 883e4f79c2
commit 12a64b1637
16 changed files with 328 additions and 289 deletions

View File

@ -18,6 +18,8 @@ module org.eclipse.jetty.http3.qpack
{
exports org.eclipse.jetty.http3.qpack;
exports org.eclipse.jetty.http3.qpack.table;
exports org.eclipse.jetty.http3.qpack.generator;
exports org.eclipse.jetty.http3.qpack.parser;
requires transitive org.eclipse.jetty.http;
requires org.slf4j;

View File

@ -1,72 +0,0 @@
//
// ========================================================================
// 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.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.NullByteBufferPool;
import org.eclipse.jetty.util.BufferUtil;
public class DecoderStream
{
private final OutputStream _outputStream;
private final ByteBufferPool _bufferPool;
public DecoderStream(OutputStream outputStream)
{
this (outputStream, new NullByteBufferPool());
}
public DecoderStream(OutputStream outputStream, ByteBufferPool bufferPool)
{
_outputStream = outputStream;
_bufferPool = bufferPool;
}
void sendSectionAcknowledgment(int streamId) throws IOException
{
int size = NBitInteger.octectsNeeded(7, streamId) + 1;
ByteBuffer buffer = _bufferPool.acquire(size, false);
BufferUtil.clearToFill(buffer);
buffer.put((byte)0x80);
NBitInteger.encode(buffer, 7, streamId);
BufferUtil.flipToFlush(buffer, 0);
BufferUtil.writeTo(buffer, _outputStream);
}
void sendStreamCancellation(int streamId) throws IOException
{
int size = NBitInteger.octectsNeeded(6, streamId) + 1;
ByteBuffer buffer = _bufferPool.acquire(size, false);
BufferUtil.clearToFill(buffer);
buffer.put((byte)0x40);
NBitInteger.encode(buffer, 6, streamId);
BufferUtil.flipToFlush(buffer, 0);
BufferUtil.writeTo(buffer, _outputStream);
}
void sendInsertCountIncrement(int increment) throws IOException
{
int size = NBitInteger.octectsNeeded(6, increment) + 1;
ByteBuffer buffer = _bufferPool.acquire(size, false);
BufferUtil.clearToFill(buffer);
buffer.put((byte)0x00);
NBitInteger.encode(buffer, 6, increment);
BufferUtil.flipToFlush(buffer, 0);
BufferUtil.writeTo(buffer, _outputStream);
}
}

View File

@ -1,135 +0,0 @@
//
// ========================================================================
// 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.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.NullByteBufferPool;
import org.eclipse.jetty.util.BufferUtil;
public class EncoderStream
{
private final OutputStream _outputStream;
private final ByteBufferPool _bufferPool;
public EncoderStream(OutputStream outputStream)
{
this (outputStream, new NullByteBufferPool());
}
public EncoderStream(OutputStream outputStream, ByteBufferPool bufferPool)
{
_outputStream = outputStream;
_bufferPool = bufferPool;
}
private boolean shouldHuffmanEncode(String s)
{
return s.length() > 128;
}
void setCapacity(int capacity) throws IOException
{
int size = NBitInteger.octectsNeeded(5, capacity) + 1;
ByteBuffer buffer = _bufferPool.acquire(size, false);
BufferUtil.clearToFill(buffer);
buffer.put((byte)0x20);
NBitInteger.encode(buffer, 5, capacity);
BufferUtil.flipToFlush(buffer, 0);
BufferUtil.writeTo(buffer, _outputStream);
}
void insertEntry(String name, String value) throws IOException
{
boolean huffmanEncodeName = shouldHuffmanEncode(name);
boolean huffmanEncodeValue = shouldHuffmanEncode(value);
int size = (huffmanEncodeName ? Huffman.octetsNeeded(name) : name.length()) +
(huffmanEncodeValue ? Huffman.octetsNeeded(value) : value.length()) + 2;
ByteBuffer buffer = _bufferPool.acquire(size, false);
BufferUtil.clearToFill(buffer);
if (huffmanEncodeName)
{
buffer.put((byte)(0x40 | 0x20));
NBitInteger.encode(buffer, 5, Huffman.octetsNeeded(name));
Huffman.encode(buffer, name);
}
else
{
buffer.put((byte)(0x40));
NBitInteger.encode(buffer, 5, name.length());
buffer.put(name.getBytes());
}
if (huffmanEncodeValue)
{
buffer.put((byte)(0x80));
NBitInteger.encode(buffer, 7, Huffman.octetsNeeded(value));
Huffman.encode(buffer, value);
}
else
{
buffer.put((byte)(0x00));
NBitInteger.encode(buffer, 5, value.length());
buffer.put(value.getBytes());
}
BufferUtil.flipToFlush(buffer, 0);
BufferUtil.writeTo(buffer, _outputStream);
}
void insertEntry(int nameRef, boolean dynamicTableReference, String value) throws IOException
{
boolean huffmanEncode = shouldHuffmanEncode(value);
int size = NBitInteger.octectsNeeded(6, nameRef) + (huffmanEncode ? Huffman.octetsNeeded(value) : value.length()) + 2;
ByteBuffer buffer = _bufferPool.acquire(size, false);
BufferUtil.clearToFill(buffer);
// First bit indicates the instruction, second bit is whether it is a dynamic table reference or not.
buffer.put((byte)(0x80 | (dynamicTableReference ? 0x00 : 0x40)));
NBitInteger.encode(buffer, 6, nameRef);
// We will not huffman encode the string.
if (huffmanEncode)
{
buffer.put((byte)(0x80));
NBitInteger.encode(buffer, 7, Huffman.octetsNeeded(value));
Huffman.encode(buffer, value);
}
else
{
buffer.put((byte)(0x00));
NBitInteger.encode(buffer, 7, value.length());
buffer.put(value.getBytes());
}
BufferUtil.flipToFlush(buffer, 0);
BufferUtil.writeTo(buffer, _outputStream);
}
void insertEntry(int ref) throws IOException
{
int size = NBitInteger.octectsNeeded(5, ref) + 1;
ByteBuffer buffer = _bufferPool.acquire(size, false);
BufferUtil.clearToFill(buffer);
buffer.put((byte)0x00);
NBitInteger.encode(buffer, 5, ref);
BufferUtil.flipToFlush(buffer, 0);
BufferUtil.writeTo(buffer, _outputStream);
}
}

View File

@ -0,0 +1,28 @@
package org.eclipse.jetty.http3.qpack.generator;
import java.nio.ByteBuffer;
import org.eclipse.jetty.http3.qpack.NBitInteger;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.BufferUtil;
public class DuplicateInstruction implements Instruction
{
private final int _index;
public DuplicateInstruction(int index)
{
_index = index;
}
@Override
public void encode(ByteBufferPool.Lease lease)
{
int size = NBitInteger.octectsNeeded(5, _index) + 1;
ByteBuffer buffer = lease.acquire(size, false);
buffer.put((byte)0x00);
NBitInteger.encode(buffer, 5, _index);
BufferUtil.flipToFlush(buffer, 0);
lease.append(buffer, true);
}
}

View File

@ -0,0 +1,52 @@
package org.eclipse.jetty.http3.qpack.generator;
import java.nio.ByteBuffer;
import org.eclipse.jetty.http3.qpack.Huffman;
import org.eclipse.jetty.http3.qpack.NBitInteger;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.BufferUtil;
public class IndexedNameEntryInstruction implements Instruction
{
private final boolean _dynamic;
private final int _index;
private final boolean _huffman;
private final String _value;
public IndexedNameEntryInstruction(boolean dynamic, int index, boolean huffman, String value)
{
_dynamic = dynamic;
_index = index;
_huffman = huffman;
_value = value;
}
@Override
public void encode(ByteBufferPool.Lease lease)
{
int size = NBitInteger.octectsNeeded(6, _index) + (_huffman ? Huffman.octetsNeeded(_value) : _value.length()) + 2;
ByteBuffer buffer = lease.acquire(size, false);
// First bit indicates the instruction, second bit is whether it is a dynamic table reference or not.
buffer.put((byte)(0x80 | (_dynamic ? 0x00 : 0x40)));
NBitInteger.encode(buffer, 6, _index);
// We will not huffman encode the string.
if (_huffman)
{
buffer.put((byte)(0x80));
NBitInteger.encode(buffer, 7, Huffman.octetsNeeded(_value));
Huffman.encode(buffer, _value);
}
else
{
buffer.put((byte)(0x00));
NBitInteger.encode(buffer, 7, _value.length());
buffer.put(_value.getBytes());
}
BufferUtil.flipToFlush(buffer, 0);
lease.append(buffer, true);
}
}

View File

@ -0,0 +1,28 @@
package org.eclipse.jetty.http3.qpack.generator;
import java.nio.ByteBuffer;
import org.eclipse.jetty.http3.qpack.NBitInteger;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.BufferUtil;
public class InsertCountIncrementInstruction implements Instruction
{
private final int _increment;
public InsertCountIncrementInstruction(int increment)
{
_increment = increment;
}
@Override
public void encode(ByteBufferPool.Lease lease)
{
int size = NBitInteger.octectsNeeded(6, _increment) + 1;
ByteBuffer buffer = lease.acquire(size, false);
buffer.put((byte)0x00);
NBitInteger.encode(buffer, 6, _increment);
BufferUtil.flipToFlush(buffer, 0);
lease.append(buffer, true);
}
}

View File

@ -0,0 +1,8 @@
package org.eclipse.jetty.http3.qpack.generator;
import org.eclipse.jetty.io.ByteBufferPool;
public interface Instruction
{
void encode(ByteBufferPool.Lease lease);
}

View File

@ -0,0 +1,61 @@
package org.eclipse.jetty.http3.qpack.generator;
import java.nio.ByteBuffer;
import org.eclipse.jetty.http3.qpack.Huffman;
import org.eclipse.jetty.http3.qpack.NBitInteger;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.BufferUtil;
public class LiteralNameEntryInstruction implements Instruction
{
private final boolean _huffmanName;
private final boolean _huffmanValue;
private final String _name;
private final String _value;
public LiteralNameEntryInstruction(boolean huffmanName, String name, boolean huffmanValue, String value)
{
_huffmanName = huffmanName;
_huffmanValue = huffmanValue;
_name = name;
_value = value;
}
@Override
public void encode(ByteBufferPool.Lease lease)
{
int size = (_huffmanName ? Huffman.octetsNeeded(_name) : _name.length()) +
(_huffmanValue ? Huffman.octetsNeeded(_value) : _value.length()) + 2;
ByteBuffer buffer = lease.acquire(size, false);
if (_huffmanName)
{
buffer.put((byte)(0x40 | 0x20));
NBitInteger.encode(buffer, 5, Huffman.octetsNeeded(_name));
Huffman.encode(buffer, _name);
}
else
{
buffer.put((byte)(0x40));
NBitInteger.encode(buffer, 5, _name.length());
buffer.put(_name.getBytes());
}
if (_huffmanValue)
{
buffer.put((byte)(0x80));
NBitInteger.encode(buffer, 7, Huffman.octetsNeeded(_value));
Huffman.encode(buffer, _value);
}
else
{
buffer.put((byte)(0x00));
NBitInteger.encode(buffer, 5, _value.length());
buffer.put(_value.getBytes());
}
BufferUtil.flipToFlush(buffer, 0);
lease.append(buffer, true);
}
}

View File

@ -0,0 +1,28 @@
package org.eclipse.jetty.http3.qpack.generator;
import java.nio.ByteBuffer;
import org.eclipse.jetty.http3.qpack.NBitInteger;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.BufferUtil;
public class SectionAcknowledgmentInstruction implements Instruction
{
private final int _streamId;
public SectionAcknowledgmentInstruction(int streamId)
{
_streamId = streamId;
}
@Override
public void encode(ByteBufferPool.Lease lease)
{
int size = NBitInteger.octectsNeeded(7, _streamId) + 1;
ByteBuffer buffer = lease.acquire(size, false);
buffer.put((byte)0x80);
NBitInteger.encode(buffer, 7, _streamId);
BufferUtil.flipToFlush(buffer, 0);
lease.append(buffer, true);
}
}

View File

@ -0,0 +1,28 @@
package org.eclipse.jetty.http3.qpack.generator;
import java.nio.ByteBuffer;
import org.eclipse.jetty.http3.qpack.NBitInteger;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.BufferUtil;
public class SetCapacityInstruction implements Instruction
{
private final int _capacity;
public SetCapacityInstruction(int capacity)
{
_capacity = capacity;
}
@Override
public void encode(ByteBufferPool.Lease lease)
{
int size = NBitInteger.octectsNeeded(5, _capacity) + 1;
ByteBuffer buffer = lease.acquire(size, false);
buffer.put((byte)0x20);
NBitInteger.encode(buffer, 5, _capacity);
BufferUtil.flipToFlush(buffer, 0);
lease.append(buffer, true);
}
}

View File

@ -0,0 +1,28 @@
package org.eclipse.jetty.http3.qpack.generator;
import java.nio.ByteBuffer;
import org.eclipse.jetty.http3.qpack.NBitInteger;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.BufferUtil;
public class StreamCancellationInstruction implements Instruction
{
private final int _streamId;
public StreamCancellationInstruction(int streamId)
{
_streamId = streamId;
}
@Override
public void encode(ByteBufferPool.Lease lease)
{
int size = NBitInteger.octectsNeeded(6, _streamId) + 1;
ByteBuffer buffer = lease.acquire(size, false);
buffer.put((byte)0x40);
NBitInteger.encode(buffer, 6, _streamId);
BufferUtil.flipToFlush(buffer, 0);
lease.append(buffer, true);
}
}

View File

@ -26,7 +26,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class IncomingEncoderStreamTest
public class DecoderInstructionParserTest
{
public static class DebugHandler implements DecoderInstructionParser.Handler
{

View File

@ -1,40 +0,0 @@
//
// ========================================================================
// 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.io.ByteArrayOutputStream;
import org.eclipse.jetty.util.TypeUtil;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalToIgnoringCase;
public class DecoderStreamTest
{
private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
private final DecoderStream decoderStream = new DecoderStream(outputStream);
@Test
public void test() throws Exception
{
decoderStream.sendSectionAcknowledgment(4);
assertThat(TypeUtil.toHexString(outputStream.toByteArray()), equalToIgnoringCase("84"));
outputStream.reset();
decoderStream.sendSectionAcknowledgment(1337);
assertThat(TypeUtil.toHexString(outputStream.toByteArray()), equalToIgnoringCase("FFBA09"));
outputStream.reset();
}
}

View File

@ -28,7 +28,7 @@ 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 class EncoderInstructionParserTest
{
public static class DebugHandler implements EncoderInstructionParser.Handler
{

View File

@ -1,40 +0,0 @@
//
// ========================================================================
// 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.io.ByteArrayOutputStream;
import org.eclipse.jetty.util.TypeUtil;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalToIgnoringCase;
public class EncoderStreamTest
{
private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
private final EncoderStream encoderStream = new EncoderStream(outputStream);
@Test
public void test() throws Exception
{
encoderStream.insertEntry(0, false, "www.example.com");
assertThat(TypeUtil.toHexString(outputStream.toByteArray()), equalToIgnoringCase("c00f7777772e6578616d706c652e636f6d"));
outputStream.reset();
encoderStream.insertEntry(1, false, "/sample/path");
assertThat(TypeUtil.toHexString(outputStream.toByteArray()), equalToIgnoringCase("c10c2f73616d706c652f70617468"));
outputStream.reset();
}
}

View File

@ -0,0 +1,63 @@
//
// ========================================================================
// 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 org.eclipse.jetty.http3.qpack.generator.IndexedNameEntryInstruction;
import org.eclipse.jetty.http3.qpack.generator.Instruction;
import org.eclipse.jetty.http3.qpack.generator.SectionAcknowledgmentInstruction;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.MappedByteBufferPool;
import org.eclipse.jetty.util.BufferUtil;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalToIgnoringCase;
import static org.hamcrest.Matchers.is;
public class InstructionGeneratorTest
{
private final ByteBufferPool _bufferPool = new MappedByteBufferPool();
private String toHexString(Instruction instruction)
{
ByteBufferPool.Lease lease = new ByteBufferPool.Lease(_bufferPool);
instruction.encode(lease);
assertThat(lease.getSize(), is(1));
return BufferUtil.toHexString(lease.getByteBuffers().get(0));
}
@Test
public void testDecoderInstructions() throws Exception
{
Instruction instruction;
instruction = new SectionAcknowledgmentInstruction(4);
assertThat(toHexString(instruction), equalToIgnoringCase("84"));
instruction = new SectionAcknowledgmentInstruction(1337);
assertThat(toHexString(instruction), equalToIgnoringCase("FFBA09"));
}
@Test
public void testEncoderInstructions() throws Exception
{
Instruction instruction;
instruction = new IndexedNameEntryInstruction(false, 0, false, "www.example.com");
assertThat(toHexString(instruction), equalToIgnoringCase("c00f7777772e6578616d706c652e636f6d"));
instruction = new IndexedNameEntryInstruction(false, 1, false, "/sample/path");
assertThat(toHexString(instruction), equalToIgnoringCase("c10c2f73616d706c652f70617468"));
}
}