Implement Encoder & Decoder Streams to encode instructions.

Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
This commit is contained in:
Lachlan Roberts 2021-02-12 20:18:24 +11:00 committed by Simone Bordet
parent 2916b60091
commit 883e4f79c2
11 changed files with 260 additions and 13 deletions

View File

@ -13,11 +13,60 @@
package org.eclipse.jetty.http3.qpack; package org.eclipse.jetty.http3.qpack;
public interface DecoderStream 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
{ {
void sendSectionAcknowledgment(int streamId); private final OutputStream _outputStream;
private final ByteBufferPool _bufferPool;
void sendStreamCancellation(int streamId); public DecoderStream(OutputStream outputStream)
{
this (outputStream, new NullByteBufferPool());
}
void sendInsertCountIncrement(int increment); 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

@ -13,13 +13,123 @@
package org.eclipse.jetty.http3.qpack; package org.eclipse.jetty.http3.qpack;
public interface EncoderStream 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
{ {
void setCapacity(int capacity); private final OutputStream _outputStream;
private final ByteBufferPool _bufferPool;
void insertEntry(String name, String value); public EncoderStream(OutputStream outputStream)
{
this (outputStream, new NullByteBufferPool());
}
void insertEntry(int nameRef, String value); public EncoderStream(OutputStream outputStream, ByteBufferPool bufferPool)
{
_outputStream = outputStream;
_bufferPool = bufferPool;
}
void insertEntry(int ref); 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

@ -11,7 +11,7 @@
// ======================================================================== // ========================================================================
// //
package org.eclipse.jetty.http3.qpack; package org.eclipse.jetty.http3.qpack.parser;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;

View File

@ -11,10 +11,12 @@
// ======================================================================== // ========================================================================
// //
package org.eclipse.jetty.http3.qpack; package org.eclipse.jetty.http3.qpack.parser;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import org.eclipse.jetty.http3.qpack.QpackException;
/** /**
* Receives instructions coming from the remote Decoder as a sequence of unframed instructions. * Receives instructions coming from the remote Decoder as a sequence of unframed instructions.
*/ */

View File

@ -11,7 +11,7 @@
// ======================================================================== // ========================================================================
// //
package org.eclipse.jetty.http3.qpack; package org.eclipse.jetty.http3.qpack.parser;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;

View File

@ -11,10 +11,13 @@
// ======================================================================== // ========================================================================
// //
package org.eclipse.jetty.http3.qpack; package org.eclipse.jetty.http3.qpack.parser;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import org.eclipse.jetty.http3.qpack.HuffmanDecoder;
import org.eclipse.jetty.http3.qpack.QpackException;
public class NBitStringParser public class NBitStringParser
{ {
private final NBitIntegerParser _integerParser; private final NBitIntegerParser _integerParser;

View File

@ -0,0 +1,40 @@
//
// ========================================================================
// 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

@ -0,0 +1,40 @@
//
// ========================================================================
// 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

@ -17,6 +17,7 @@ import java.nio.ByteBuffer;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.Queue; import java.util.Queue;
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.BeforeEach;

View File

@ -17,6 +17,7 @@ import java.nio.ByteBuffer;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.Queue; import java.util.Queue;
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.Test; import org.junit.jupiter.api.Test;

View File

@ -15,6 +15,7 @@ package org.eclipse.jetty.http3.qpack;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import org.eclipse.jetty.http3.qpack.parser.NBitIntegerParser;
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.Test; import org.junit.jupiter.api.Test;