Issue #6728 - Fixes for QPACK

Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
This commit is contained in:
Lachlan Roberts 2021-09-10 14:31:48 +10:00 committed by Simone Bordet
parent 0d2fe5657a
commit 80d2cee238
36 changed files with 445 additions and 191 deletions

View File

@ -1,3 +1,16 @@
//
// ========================================================================
// 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;
public enum ErrorCode

View File

@ -1,3 +1,16 @@
//
// ========================================================================
// 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.internal.parser;
import java.nio.ByteBuffer;

View File

@ -43,13 +43,32 @@ public class QpackDecoder implements Dumpable
public static final Logger LOG = LoggerFactory.getLogger(QpackDecoder.class);
private final List<Instruction> _instructions = new ArrayList<>();
private final List<MetaDataNotification> _metaDataNotifications = new ArrayList<>();
private final Instruction.Handler _handler;
private final QpackContext _context;
private final DecoderInstructionParser _parser;
private final List<EncodedFieldSection> _encodedFieldSections = new ArrayList<>();
private final NBitIntegerParser _integerDecoder = new NBitIntegerParser();
private final int _maxHeaderSize;
private MetaData _metaData;
private static class MetaDataNotification
{
private final MetaData _metaData;
private final Handler _handler;
private final long _streamId;
public MetaDataNotification(long streamId, MetaData metaData, Handler handler)
{
_streamId = streamId;
_metaData = metaData;
_handler = handler;
}
public void notifyHandler()
{
_handler.onMetaData(_streamId, _metaData);
}
}
/**
* @param maxHeaderSize The maximum allowed size of a headers block, expressed as total of all name and value characters, plus 32 per field
@ -82,13 +101,13 @@ public class QpackDecoder implements Dumpable
throw new QpackException.SessionException("431 Request Header Fields too large");
_integerDecoder.setPrefix(8);
int encodedInsertCount = _integerDecoder.decode(buffer);
int encodedInsertCount = _integerDecoder.decodeInt(buffer);
if (encodedInsertCount < 0)
throw new QpackException.CompressionException("Could not parse Required Insert Count");
_integerDecoder.setPrefix(7);
boolean signBit = (buffer.get(buffer.position()) & 0x80) != 0;
int deltaBase = _integerDecoder.decode(buffer);
int deltaBase = _integerDecoder.decodeInt(buffer);
if (deltaBase < 0)
throw new QpackException.CompressionException("Could not parse Delta Base");
@ -100,15 +119,15 @@ public class QpackDecoder implements Dumpable
// Parse the buffer into an Encoded Field Section.
int base = signBit ? requiredInsertCount - deltaBase - 1 : requiredInsertCount + deltaBase;
EncodedFieldSection encodedFieldSection = new EncodedFieldSection(streamId, requiredInsertCount, base, buffer);
EncodedFieldSection encodedFieldSection = new EncodedFieldSection(streamId, handler, requiredInsertCount, base, buffer);
// Decode it straight away if we can, otherwise add it to the list of EncodedFieldSections.
if (requiredInsertCount <= insertCount)
{
_metaData = encodedFieldSection.decode(_context, _maxHeaderSize);
MetaData metaData = encodedFieldSection.decode(_context, _maxHeaderSize);
if (LOG.isDebugEnabled())
LOG.debug("Decoded: streamId={}, metadata={}", streamId, _metaData);
LOG.debug("Decoded: streamId={}, metadata={}", streamId, metaData);
_metaDataNotifications.add(new MetaDataNotification(streamId, metaData, handler));
_instructions.add(new SectionAcknowledgmentInstruction(streamId));
}
else
@ -118,21 +137,21 @@ public class QpackDecoder implements Dumpable
_encodedFieldSections.add(encodedFieldSection);
}
if (!_instructions.isEmpty())
_handler.onInstructions(_instructions);
_instructions.clear();
MetaData metaData = _metaData;
_metaData = null;
if (metaData != null)
handler.onMetaData(streamId, metaData);
return metaData != null;
boolean hadMetaData = !_metaDataNotifications.isEmpty();
notifyInstructionHandler();
notifyMetaDataHandler();
return hadMetaData;
}
void parseInstruction(ByteBuffer buffer) throws QpackException
public void parseInstructionBuffer(ByteBuffer buffer) throws QpackException
{
_parser.parse(buffer);
while (BufferUtil.hasContent(buffer))
{
_parser.parse(buffer);
}
notifyInstructionHandler();
notifyMetaDataHandler();
}
private void checkEncodedFieldSections() throws QpackException
@ -143,10 +162,11 @@ public class QpackDecoder implements Dumpable
if (encodedFieldSection.getRequiredInsertCount() <= insertCount)
{
long streamId = encodedFieldSection.getStreamId();
_metaData = encodedFieldSection.decode(_context, _maxHeaderSize);
MetaData metaData = encodedFieldSection.decode(_context, _maxHeaderSize);
if (LOG.isDebugEnabled())
LOG.debug("Decoded: streamId={}, metadata={}", streamId, _metaData);
LOG.debug("Decoded: streamId={}, metadata={}", streamId, metaData);
_metaDataNotifications.add(new MetaDataNotification(streamId, metaData, encodedFieldSection.getHandler()));
_instructions.add(new SectionAcknowledgmentInstruction(streamId));
}
}
@ -244,6 +264,22 @@ public class QpackDecoder implements Dumpable
return String.format("QpackDecoder@%x{%s}", hashCode(), _context);
}
private void notifyInstructionHandler() throws QpackException
{
if (!_instructions.isEmpty())
_handler.onInstructions(_instructions);
_instructions.clear();
}
private void notifyMetaDataHandler()
{
for (MetaDataNotification notification : _metaDataNotifications)
{
notification.notifyHandler();
}
_metaDataNotifications.clear();
}
/**
* This delivers notifications from the DecoderInstruction parser directly into the Decoder.
*/

View File

@ -36,7 +36,7 @@ import org.eclipse.jetty.http3.qpack.internal.metadata.Http3Fields;
import org.eclipse.jetty.http3.qpack.internal.parser.EncoderInstructionParser;
import org.eclipse.jetty.http3.qpack.internal.table.DynamicTable;
import org.eclipse.jetty.http3.qpack.internal.table.Entry;
import org.eclipse.jetty.http3.qpack.internal.util.NBitLongEncoder;
import org.eclipse.jetty.http3.qpack.internal.util.NBitIntegerEncoder;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.component.Dumpable;
import org.slf4j.Logger;
@ -85,7 +85,7 @@ public class QpackEncoder implements Dumpable
private final Instruction.Handler _handler;
private final QpackContext _context;
private final int _maxBlockedStreams;
private final Map<Integer, StreamInfo> _streamInfoMap = new HashMap<>();
private final Map<Long, StreamInfo> _streamInfoMap = new HashMap<>();
private final EncoderInstructionParser _parser;
private int _knownInsertCount = 0;
private int _blockedStreams = 0;
@ -118,12 +118,16 @@ public class QpackEncoder implements Dumpable
return !DO_NOT_HUFFMAN.contains(httpField.getHeader());
}
public void parseInstruction(ByteBuffer buffer) throws QpackException
public void parseInstructionBuffer(ByteBuffer buffer) throws QpackException
{
_parser.parse(buffer);
while (BufferUtil.hasContent(buffer))
{
_parser.parse(buffer);
}
notifyInstructionHandler();
}
boolean insert(HttpField field) throws QpackException
public boolean insert(HttpField field) throws QpackException
{
DynamicTable dynamicTable = _context.getDynamicTable();
@ -141,6 +145,7 @@ public class QpackEncoder implements Dumpable
int index = _context.indexOf(entry);
dynamicTable.add(new Entry(field));
_instructions.add(new DuplicateInstruction(index));
notifyInstructionHandler();
return true;
}
@ -151,19 +156,18 @@ public class QpackEncoder implements Dumpable
int index = _context.indexOf(nameEntry);
dynamicTable.add(new Entry(field));
_instructions.add(new IndexedNameEntryInstruction(!nameEntry.isStatic(), index, huffman, field.getValue()));
notifyInstructionHandler();
return true;
}
dynamicTable.add(new Entry(field));
_instructions.add(new LiteralNameEntryInstruction(field, huffman));
_handler.onInstructions(_instructions);
_instructions.clear();
notifyInstructionHandler();
return true;
}
public void encode(ByteBuffer buffer, int streamId, MetaData metadata) throws QpackException
public void encode(ByteBuffer buffer, long streamId, MetaData metadata) throws QpackException
{
if (LOG.isDebugEnabled())
LOG.debug("Encoding: streamId={}, metadata={}", streamId, metadata);
@ -217,9 +221,9 @@ public class QpackEncoder implements Dumpable
int pos = BufferUtil.flipToFill(buffer);
// Encode the Field Section Prefix into the ByteBuffer.
NBitLongEncoder.encode(buffer, 8, encodedInsertCount);
NBitIntegerEncoder.encode(buffer, 8, encodedInsertCount);
buffer.put(signBit ? (byte)0x80 : (byte)0x00);
NBitLongEncoder.encode(buffer, 7, deltaBase);
NBitIntegerEncoder.encode(buffer, 7, deltaBase);
// Encode the field lines into the ByteBuffer.
for (EncodableEntry entry : encodableEntries)
@ -228,10 +232,7 @@ public class QpackEncoder implements Dumpable
}
BufferUtil.flipToFlush(buffer, pos);
if (!_instructions.isEmpty())
_handler.onInstructions(_instructions);
_instructions.clear();
notifyInstructionHandler();
}
private EncodableEntry encode(StreamInfo streamInfo, HttpField field)
@ -316,7 +317,7 @@ public class QpackEncoder implements Dumpable
_knownInsertCount += increment;
}
void sectionAcknowledgement(int streamId) throws QpackException
void sectionAcknowledgement(long streamId) throws QpackException
{
if (LOG.isDebugEnabled())
LOG.debug("SectionAcknowledgement: streamId={}", streamId);
@ -335,7 +336,7 @@ public class QpackEncoder implements Dumpable
_streamInfoMap.remove(streamId);
}
void streamCancellation(int streamId) throws QpackException
void streamCancellation(long streamId) throws QpackException
{
if (LOG.isDebugEnabled())
LOG.debug("StreamCancellation: streamId={}", streamId);
@ -400,16 +401,23 @@ public class QpackEncoder implements Dumpable
return (reqInsertCount % (2 * maxEntries)) + 1;
}
private void notifyInstructionHandler() throws QpackException
{
if (!_instructions.isEmpty())
_handler.onInstructions(_instructions);
_instructions.clear();
}
public class EncoderAdapter implements EncoderInstructionParser.Handler
{
@Override
public void onSectionAcknowledgement(int streamId) throws QpackException
public void onSectionAcknowledgement(long streamId) throws QpackException
{
sectionAcknowledgement(streamId);
}
@Override
public void onStreamCancellation(int streamId) throws QpackException
public void onStreamCancellation(long streamId) throws QpackException
{
streamCancellation(streamId);
}

View File

@ -21,7 +21,7 @@ import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.PreEncodedHttpField;
import org.eclipse.jetty.http3.qpack.internal.table.Entry;
import org.eclipse.jetty.http3.qpack.internal.util.HuffmanEncoder;
import org.eclipse.jetty.http3.qpack.internal.util.NBitLongEncoder;
import org.eclipse.jetty.http3.qpack.internal.util.NBitIntegerEncoder;
public abstract class EncodableEntry
{
@ -69,21 +69,21 @@ public abstract class EncodableEntry
// Indexed Field Line with Static Reference.
buffer.put((byte)(0x80 | 0x40));
int relativeIndex = _entry.getIndex();
NBitLongEncoder.encode(buffer, 6, relativeIndex);
NBitIntegerEncoder.encode(buffer, 6, relativeIndex);
}
else if (_entry.getIndex() < base)
{
// Indexed Field Line with Dynamic Reference.
buffer.put((byte)0x80);
int relativeIndex = base - (_entry.getIndex() + 1);
NBitLongEncoder.encode(buffer, 6, relativeIndex);
NBitIntegerEncoder.encode(buffer, 6, relativeIndex);
}
else
{
// Indexed Field Line with Post-Base Index.
buffer.put((byte)0x10);
int relativeIndex = _entry.getIndex() - base;
NBitLongEncoder.encode(buffer, 4, relativeIndex);
NBitIntegerEncoder.encode(buffer, 4, relativeIndex);
}
}
@ -95,19 +95,19 @@ public abstract class EncodableEntry
{
// Indexed Field Line with Static Reference.
int relativeIndex = _entry.getIndex();
return 1 + NBitLongEncoder.octectsNeeded(6, relativeIndex);
return 1 + NBitIntegerEncoder.octectsNeeded(6, relativeIndex);
}
else if (_entry.getIndex() < base)
{
// Indexed Field Line with Dynamic Reference.
int relativeIndex = base - (_entry.getIndex() + 1);
return 1 + NBitLongEncoder.octectsNeeded(6, relativeIndex);
return 1 + NBitIntegerEncoder.octectsNeeded(6, relativeIndex);
}
else
{
// Indexed Field Line with Post-Base Index.
int relativeIndex = _entry.getIndex() - base;
return 1 + NBitLongEncoder.octectsNeeded(4, relativeIndex);
return 1 + NBitIntegerEncoder.octectsNeeded(4, relativeIndex);
}
}
@ -144,21 +144,21 @@ public abstract class EncodableEntry
// Literal Field Line with Static Name Reference.
buffer.put((byte)(0x40 | 0x10 | (allowIntermediary ? 0x20 : 0x00)));
int relativeIndex = _nameEntry.getIndex();
NBitLongEncoder.encode(buffer, 4, relativeIndex);
NBitIntegerEncoder.encode(buffer, 4, relativeIndex);
}
else if (_nameEntry.getIndex() < base)
{
// Literal Field Line with Dynamic Name Reference.
buffer.put((byte)(0x40 | (allowIntermediary ? 0x20 : 0x00)));
int relativeIndex = base - (_nameEntry.getIndex() + 1);
NBitLongEncoder.encode(buffer, 4, relativeIndex);
NBitIntegerEncoder.encode(buffer, 4, relativeIndex);
}
else
{
// Literal Field Line with Post-Base Name Reference.
buffer.put((byte)(allowIntermediary ? 0x08 : 0x00));
int relativeIndex = _nameEntry.getIndex() - base;
NBitLongEncoder.encode(buffer, 3, relativeIndex);
NBitIntegerEncoder.encode(buffer, 3, relativeIndex);
}
// Encode the value.
@ -166,13 +166,13 @@ public abstract class EncodableEntry
if (_huffman)
{
buffer.put((byte)0x80);
NBitLongEncoder.encode(buffer, 7, HuffmanEncoder.octetsNeeded(value));
NBitIntegerEncoder.encode(buffer, 7, HuffmanEncoder.octetsNeeded(value));
HuffmanEncoder.encode(buffer, value);
}
else
{
buffer.put((byte)0x00);
NBitLongEncoder.encode(buffer, 7, value.length());
NBitIntegerEncoder.encode(buffer, 7, value.length());
buffer.put(value.getBytes());
}
}
@ -183,7 +183,7 @@ public abstract class EncodableEntry
String value = getValue();
int relativeIndex = _nameEntry.getIndex() - base;
int valueLength = _huffman ? HuffmanEncoder.octetsNeeded(value) : value.length();
return 1 + NBitLongEncoder.octectsNeeded(4, relativeIndex) + 1 + NBitLongEncoder.octectsNeeded(7, valueLength) + valueLength;
return 1 + NBitIntegerEncoder.octectsNeeded(4, relativeIndex) + 1 + NBitIntegerEncoder.octectsNeeded(7, valueLength) + valueLength;
}
@Override
@ -221,20 +221,20 @@ public abstract class EncodableEntry
if (_huffman)
{
buffer.put((byte)(0x28 | allowIntermediary));
NBitLongEncoder.encode(buffer, 3, HuffmanEncoder.octetsNeeded(name));
NBitIntegerEncoder.encode(buffer, 3, HuffmanEncoder.octetsNeeded(name));
HuffmanEncoder.encode(buffer, name);
buffer.put((byte)0x80);
NBitLongEncoder.encode(buffer, 7, HuffmanEncoder.octetsNeeded(value));
NBitIntegerEncoder.encode(buffer, 7, HuffmanEncoder.octetsNeeded(value));
HuffmanEncoder.encode(buffer, value);
}
else
{
// TODO: What charset should we be using? (this applies to the instruction generators as well).
buffer.put((byte)(0x20 | allowIntermediary));
NBitLongEncoder.encode(buffer, 3, name.length());
NBitIntegerEncoder.encode(buffer, 3, name.length());
buffer.put(name.getBytes());
buffer.put((byte)0x00);
NBitLongEncoder.encode(buffer, 7, value.length());
NBitIntegerEncoder.encode(buffer, 7, value.length());
buffer.put(value.getBytes());
}
}
@ -246,7 +246,7 @@ public abstract class EncodableEntry
String value = getValue();
int nameLength = _huffman ? HuffmanEncoder.octetsNeeded(name) : name.length();
int valueLength = _huffman ? HuffmanEncoder.octetsNeeded(value) : value.length();
return 2 + NBitLongEncoder.octectsNeeded(3, nameLength) + nameLength + NBitLongEncoder.octectsNeeded(7, valueLength) + valueLength;
return 2 + NBitIntegerEncoder.octectsNeeded(3, nameLength) + nameLength + NBitIntegerEncoder.octectsNeeded(7, valueLength) + valueLength;
}
@Override

View File

@ -23,15 +23,15 @@ import org.eclipse.jetty.http3.qpack.internal.table.Entry;
public class StreamInfo implements Iterable<StreamInfo.SectionInfo>
{
private final int _streamId;
private final long _streamId;
private final Queue<SectionInfo> _sectionInfos = new LinkedList<>();
public StreamInfo(int streamId)
public StreamInfo(long streamId)
{
_streamId = streamId;
}
public int getStreamId()
public long getStreamId()
{
return _streamId;
}

View File

@ -16,7 +16,7 @@ package org.eclipse.jetty.http3.qpack.internal.instruction;
import java.nio.ByteBuffer;
import org.eclipse.jetty.http3.qpack.Instruction;
import org.eclipse.jetty.http3.qpack.internal.util.NBitLongEncoder;
import org.eclipse.jetty.http3.qpack.internal.util.NBitIntegerEncoder;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.BufferUtil;
@ -37,10 +37,10 @@ public class DuplicateInstruction implements Instruction
@Override
public void encode(ByteBufferPool.Lease lease)
{
int size = NBitLongEncoder.octectsNeeded(5, _index) + 1;
int size = NBitIntegerEncoder.octectsNeeded(5, _index) + 1;
ByteBuffer buffer = lease.acquire(size, false);
buffer.put((byte)0x00);
NBitLongEncoder.encode(buffer, 5, _index);
NBitIntegerEncoder.encode(buffer, 5, _index);
BufferUtil.flipToFlush(buffer, 0);
lease.append(buffer, true);
}

View File

@ -17,7 +17,7 @@ import java.nio.ByteBuffer;
import org.eclipse.jetty.http3.qpack.Instruction;
import org.eclipse.jetty.http3.qpack.internal.util.HuffmanEncoder;
import org.eclipse.jetty.http3.qpack.internal.util.NBitLongEncoder;
import org.eclipse.jetty.http3.qpack.internal.util.NBitIntegerEncoder;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.BufferUtil;
@ -54,24 +54,24 @@ public class IndexedNameEntryInstruction implements Instruction
@Override
public void encode(ByteBufferPool.Lease lease)
{
int size = NBitLongEncoder.octectsNeeded(6, _index) + (_huffman ? HuffmanEncoder.octetsNeeded(_value) : _value.length()) + 2;
int size = NBitIntegerEncoder.octectsNeeded(6, _index) + (_huffman ? HuffmanEncoder.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)));
NBitLongEncoder.encode(buffer, 6, _index);
NBitIntegerEncoder.encode(buffer, 6, _index);
// We will not huffman encode the string.
if (_huffman)
{
buffer.put((byte)(0x80));
NBitLongEncoder.encode(buffer, 7, HuffmanEncoder.octetsNeeded(_value));
NBitIntegerEncoder.encode(buffer, 7, HuffmanEncoder.octetsNeeded(_value));
HuffmanEncoder.encode(buffer, _value);
}
else
{
buffer.put((byte)(0x00));
NBitLongEncoder.encode(buffer, 7, _value.length());
NBitIntegerEncoder.encode(buffer, 7, _value.length());
buffer.put(_value.getBytes());
}

View File

@ -16,7 +16,7 @@ package org.eclipse.jetty.http3.qpack.internal.instruction;
import java.nio.ByteBuffer;
import org.eclipse.jetty.http3.qpack.Instruction;
import org.eclipse.jetty.http3.qpack.internal.util.NBitLongEncoder;
import org.eclipse.jetty.http3.qpack.internal.util.NBitIntegerEncoder;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.BufferUtil;
@ -32,10 +32,10 @@ public class InsertCountIncrementInstruction implements Instruction
@Override
public void encode(ByteBufferPool.Lease lease)
{
int size = NBitLongEncoder.octectsNeeded(6, _increment) + 1;
int size = NBitIntegerEncoder.octectsNeeded(6, _increment) + 1;
ByteBuffer buffer = lease.acquire(size, false);
buffer.put((byte)0x00);
NBitLongEncoder.encode(buffer, 6, _increment);
NBitIntegerEncoder.encode(buffer, 6, _increment);
BufferUtil.flipToFlush(buffer, 0);
lease.append(buffer, true);
}

View File

@ -18,7 +18,7 @@ import java.nio.ByteBuffer;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http3.qpack.Instruction;
import org.eclipse.jetty.http3.qpack.internal.util.HuffmanEncoder;
import org.eclipse.jetty.http3.qpack.internal.util.NBitLongEncoder;
import org.eclipse.jetty.http3.qpack.internal.util.NBitIntegerEncoder;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.BufferUtil;
@ -62,26 +62,26 @@ public class LiteralNameEntryInstruction implements Instruction
if (_huffmanName)
{
buffer.put((byte)(0x40 | 0x20));
NBitLongEncoder.encode(buffer, 5, HuffmanEncoder.octetsNeeded(_name));
NBitIntegerEncoder.encode(buffer, 5, HuffmanEncoder.octetsNeeded(_name));
HuffmanEncoder.encode(buffer, _name);
}
else
{
buffer.put((byte)(0x40));
NBitLongEncoder.encode(buffer, 5, _name.length());
NBitIntegerEncoder.encode(buffer, 5, _name.length());
buffer.put(_name.getBytes());
}
if (_huffmanValue)
{
buffer.put((byte)(0x80));
NBitLongEncoder.encode(buffer, 7, HuffmanEncoder.octetsNeeded(_value));
NBitIntegerEncoder.encode(buffer, 7, HuffmanEncoder.octetsNeeded(_value));
HuffmanEncoder.encode(buffer, _value);
}
else
{
buffer.put((byte)(0x00));
NBitLongEncoder.encode(buffer, 5, _value.length());
NBitIntegerEncoder.encode(buffer, 5, _value.length());
buffer.put(_value.getBytes());
}

View File

@ -16,7 +16,7 @@ package org.eclipse.jetty.http3.qpack.internal.instruction;
import java.nio.ByteBuffer;
import org.eclipse.jetty.http3.qpack.Instruction;
import org.eclipse.jetty.http3.qpack.internal.util.NBitLongEncoder;
import org.eclipse.jetty.http3.qpack.internal.util.NBitIntegerEncoder;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.BufferUtil;
@ -32,10 +32,10 @@ public class SectionAcknowledgmentInstruction implements Instruction
@Override
public void encode(ByteBufferPool.Lease lease)
{
int size = NBitLongEncoder.octectsNeeded(7, _streamId) + 1;
int size = NBitIntegerEncoder.octectsNeeded(7, _streamId) + 1;
ByteBuffer buffer = lease.acquire(size, false);
buffer.put((byte)0x80);
NBitLongEncoder.encode(buffer, 7, _streamId);
NBitIntegerEncoder.encode(buffer, 7, _streamId);
BufferUtil.flipToFlush(buffer, 0);
lease.append(buffer, true);
}

View File

@ -16,7 +16,7 @@ package org.eclipse.jetty.http3.qpack.internal.instruction;
import java.nio.ByteBuffer;
import org.eclipse.jetty.http3.qpack.Instruction;
import org.eclipse.jetty.http3.qpack.internal.util.NBitLongEncoder;
import org.eclipse.jetty.http3.qpack.internal.util.NBitIntegerEncoder;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.BufferUtil;
@ -37,10 +37,10 @@ public class SetCapacityInstruction implements Instruction
@Override
public void encode(ByteBufferPool.Lease lease)
{
int size = NBitLongEncoder.octectsNeeded(5, _capacity) + 1;
int size = NBitIntegerEncoder.octectsNeeded(5, _capacity) + 1;
ByteBuffer buffer = lease.acquire(size, false);
buffer.put((byte)0x20);
NBitLongEncoder.encode(buffer, 5, _capacity);
NBitIntegerEncoder.encode(buffer, 5, _capacity);
BufferUtil.flipToFlush(buffer, 0);
lease.append(buffer, true);
}

View File

@ -16,7 +16,7 @@ package org.eclipse.jetty.http3.qpack.internal.instruction;
import java.nio.ByteBuffer;
import org.eclipse.jetty.http3.qpack.Instruction;
import org.eclipse.jetty.http3.qpack.internal.util.NBitLongEncoder;
import org.eclipse.jetty.http3.qpack.internal.util.NBitIntegerEncoder;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.BufferUtil;
@ -32,10 +32,10 @@ public class StreamCancellationInstruction implements Instruction
@Override
public void encode(ByteBufferPool.Lease lease)
{
int size = NBitLongEncoder.octectsNeeded(6, _streamId) + 1;
int size = NBitIntegerEncoder.octectsNeeded(6, _streamId) + 1;
ByteBuffer buffer = lease.acquire(size, false);
buffer.put((byte)0x40);
NBitLongEncoder.encode(buffer, 6, _streamId);
NBitIntegerEncoder.encode(buffer, 6, _streamId);
BufferUtil.flipToFlush(buffer, 0);
lease.append(buffer, true);
}

View File

@ -137,7 +137,7 @@ public class DecoderInstructionParser
continue;
case INDEX:
_index = _integerParser.decode(buffer);
_index = _integerParser.decodeInt(buffer);
if (_index < 0)
return;
@ -200,7 +200,7 @@ public class DecoderInstructionParser
private void parseDuplicate(ByteBuffer buffer) throws QpackException
{
int index = _integerParser.decode(buffer);
int index = _integerParser.decodeInt(buffer);
if (index >= 0)
{
reset();
@ -210,7 +210,7 @@ public class DecoderInstructionParser
private void parseSetDynamicTableCapacity(ByteBuffer buffer) throws QpackException
{
int capacity = _integerParser.decode(buffer);
int capacity = _integerParser.decodeInt(buffer);
if (capacity >= 0)
{
reset();

View File

@ -19,6 +19,7 @@ import java.util.List;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http3.qpack.QpackDecoder;
import org.eclipse.jetty.http3.qpack.QpackException;
import org.eclipse.jetty.http3.qpack.internal.QpackContext;
import org.eclipse.jetty.http3.qpack.internal.metadata.MetaDataBuilder;
@ -39,12 +40,14 @@ public class EncodedFieldSection
private final long _streamId;
private final int _requiredInsertCount;
private final int _base;
private final QpackDecoder.Handler _handler;
public EncodedFieldSection(long streamId, int requiredInsertCount, int base, ByteBuffer content) throws QpackException
public EncodedFieldSection(long streamId, QpackDecoder.Handler handler, int requiredInsertCount, int base, ByteBuffer content) throws QpackException
{
_streamId = streamId;
_requiredInsertCount = requiredInsertCount;
_base = base;
_handler = handler;
while (content.hasRemaining())
{
@ -70,6 +73,11 @@ public class EncodedFieldSection
return _streamId;
}
public QpackDecoder.Handler getHandler()
{
return _handler;
}
public int getRequiredInsertCount()
{
return _requiredInsertCount;
@ -94,7 +102,7 @@ public class EncodedFieldSection
byte firstByte = buffer.get(buffer.position());
boolean dynamicTable = (firstByte & 0x40) == 0;
_integerParser.setPrefix(6);
int index = _integerParser.decode(buffer);
int index = _integerParser.decodeInt(buffer);
if (index < 0)
throw new QpackException.CompressionException("Invalid Index");
return new IndexedField(dynamicTable, index);
@ -103,7 +111,7 @@ public class EncodedFieldSection
private EncodedField parseIndexedFieldPostBase(ByteBuffer buffer) throws QpackException
{
_integerParser.setPrefix(4);
int index = _integerParser.decode(buffer);
int index = _integerParser.decodeInt(buffer);
if (index < 0)
throw new QpackException.CompressionException("Invalid Index");
@ -119,7 +127,7 @@ public class EncodedFieldSection
boolean dynamicTable = (firstByte & 0x10) == 0;
_integerParser.setPrefix(4);
int nameIndex = _integerParser.decode(buffer);
int nameIndex = _integerParser.decodeInt(buffer);
if (nameIndex < 0)
throw new QpackException.CompressionException("Invalid Name Index");
@ -137,7 +145,7 @@ public class EncodedFieldSection
boolean allowEncoding = (firstByte & 0x08) != 0;
_integerParser.setPrefix(3);
int nameIndex = _integerParser.decode(buffer);
int nameIndex = _integerParser.decodeInt(buffer);
if (nameIndex < 0)
throw new QpackException.CompressionException("Invalid Index");

View File

@ -41,9 +41,9 @@ public class EncoderInstructionParser
public interface Handler
{
void onSectionAcknowledgement(int streamId) throws QpackException;
void onSectionAcknowledgement(long streamId) throws QpackException;
void onStreamCancellation(int streamId) throws QpackException;
void onStreamCancellation(long streamId) throws QpackException;
void onInsertCountIncrement(int increment) throws QpackException;
}
@ -103,7 +103,7 @@ public class EncoderInstructionParser
private void parseSectionAcknowledgment(ByteBuffer buffer) throws QpackException
{
int streamId = _integerParser.decode(buffer);
long streamId = _integerParser.decodeInt(buffer);
if (streamId >= 0)
{
reset();
@ -113,7 +113,7 @@ public class EncoderInstructionParser
private void parseStreamCancellation(ByteBuffer buffer) throws QpackException
{
int streamId = _integerParser.decode(buffer);
long streamId = _integerParser.decodeLong(buffer);
if (streamId >= 0)
{
reset();
@ -123,7 +123,7 @@ public class EncoderInstructionParser
private void parseInsertCountIncrement(ByteBuffer buffer) throws QpackException
{
int increment = _integerParser.decode(buffer);
int increment = _integerParser.decodeInt(buffer);
if (increment >= 0)
{
reset();

View File

@ -18,7 +18,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http3.qpack.internal.util.HuffmanEncoder;
import org.eclipse.jetty.http3.qpack.internal.util.NBitLongEncoder;
import org.eclipse.jetty.http3.qpack.internal.util.NBitIntegerEncoder;
import org.eclipse.jetty.util.StringUtil;
public class Entry
@ -119,14 +119,14 @@ public class Entry
int huffmanLen = HuffmanEncoder.octetsNeeded(value);
if (huffmanLen < 0)
throw new IllegalStateException("bad value");
int lenLen = NBitLongEncoder.octectsNeeded(7, huffmanLen);
int lenLen = NBitIntegerEncoder.octectsNeeded(7, huffmanLen);
_huffmanValue = new byte[1 + lenLen + huffmanLen];
ByteBuffer buffer = ByteBuffer.wrap(_huffmanValue);
// Indicate Huffman
buffer.put((byte)0x80);
// Add huffman length
NBitLongEncoder.encode(buffer, 7, huffmanLen);
NBitIntegerEncoder.encode(buffer, 7, huffmanLen);
// Encode value
HuffmanEncoder.encode(buffer, value);
}

View File

@ -15,7 +15,7 @@ package org.eclipse.jetty.http3.qpack.internal.util;
import java.nio.ByteBuffer;
public class NBitLongEncoder
public class NBitIntegerEncoder
{
public static int octectsNeeded(int n, long i)
{

View File

@ -18,8 +18,8 @@ import java.nio.ByteBuffer;
public class NBitIntegerParser
{
private int _prefix;
private int _total;
private int _multiplier;
private long _total;
private long _multiplier;
private boolean _started;
public void setPrefix(int prefix)
@ -29,7 +29,12 @@ public class NBitIntegerParser
_prefix = prefix;
}
public int decode(ByteBuffer buffer)
public int decodeInt(ByteBuffer buffer)
{
return Math.toIntExact(decodeLong(buffer));
}
public long decodeLong(ByteBuffer buffer)
{
if (!_started)
{
@ -42,7 +47,7 @@ public class NBitIntegerParser
_total = buffer.get() & nbits;
if (_total < nbits)
{
int total = _total;
long total = _total;
reset();
return total;
}
@ -55,11 +60,11 @@ public class NBitIntegerParser
return -1;
int b = buffer.get() & 0xFF;
_total += (b & 127) * _multiplier;
_multiplier = _multiplier * 128;
_total = Math.addExact(_total, (b & 127) * _multiplier);
_multiplier = Math.multiplyExact(_multiplier, 128);
if ((b & 128) == 0)
{
int total = _total;
long total = _total;
reset();
return total;
}

View File

@ -64,7 +64,7 @@ public class NBitStringParser
continue;
case LENGTH:
_length = _integerParser.decode(buffer);
_length = _integerParser.decodeInt(buffer);
if (_length < 0)
return null;
_state = State.VALUE;

View File

@ -75,7 +75,7 @@ public class EncodeDecodeTest
public void test() throws Exception
{
// B.1. Literal Field Line With Name Reference.
int streamId = 0;
long streamId = 0;
HttpFields httpFields = HttpFields.build().add(":path", "/index.html");
ByteBuffer buffer = BufferUtil.allocate(1024);

View File

@ -44,35 +44,35 @@ public class EncoderInstructionParserTest
String encoded = "84";
ByteBuffer buffer = BufferUtil.toBuffer(TypeUtil.fromHexString(encoded));
_instructionParser.parse(buffer);
assertThat(_handler.sectionAcknowledgements.poll(), is(4));
assertThat(_handler.sectionAcknowledgements.poll(), is(4L));
assertTrue(_handler.isEmpty());
// 1111 1110 == FE is largest value we can do without continuation should be stream ID 126.
encoded = "FE";
buffer = BufferUtil.toBuffer(TypeUtil.fromHexString(encoded));
_instructionParser.parse(buffer);
assertThat(_handler.sectionAcknowledgements.poll(), is(126));
assertThat(_handler.sectionAcknowledgements.poll(), is(126L));
assertTrue(_handler.isEmpty());
// 1111 1111 0000 0000 == FF00 is next value, stream id 127.
encoded = "FF00";
buffer = BufferUtil.toBuffer(TypeUtil.fromHexString(encoded));
_instructionParser.parse(buffer);
assertThat(_handler.sectionAcknowledgements.poll(), is(127));
assertThat(_handler.sectionAcknowledgements.poll(), is(127L));
assertTrue(_handler.isEmpty());
// 1111 1111 0000 0001 == FF01 is next value, stream id 128.
encoded = "FF01";
buffer = BufferUtil.toBuffer(TypeUtil.fromHexString(encoded));
_instructionParser.parse(buffer);
assertThat(_handler.sectionAcknowledgements.poll(), is(128));
assertThat(_handler.sectionAcknowledgements.poll(), is(128L));
assertTrue(_handler.isEmpty());
// FFBA09 contains section ack with stream ID of 1337, this contains an octet with continuation bit.
encoded = "FFBA09";
buffer = BufferUtil.toBuffer(TypeUtil.fromHexString(encoded));
_instructionParser.parse(buffer);
assertThat(_handler.sectionAcknowledgements.poll(), is(1337));
assertThat(_handler.sectionAcknowledgements.poll(), is(1337L));
assertTrue(_handler.isEmpty());
// Test with continuation.
@ -85,7 +85,7 @@ public class EncoderInstructionParserTest
_instructionParser.parse(buffer1);
assertTrue(_handler.isEmpty());
_instructionParser.parse(buffer2);
assertThat(_handler.sectionAcknowledgements.poll(), is(1337));
assertThat(_handler.sectionAcknowledgements.poll(), is(1337L));
assertTrue(_handler.isEmpty());
}
}
@ -96,7 +96,7 @@ public class EncoderInstructionParserTest
// Stream Cancellation (Stream=8).
ByteBuffer buffer = QpackTestUtil.hexToBuffer("48");
_instructionParser.parse(buffer);
assertThat(_handler.streamCancellations.poll(), is(8));
assertThat(_handler.streamCancellations.poll(), is(8L));
assertTrue(_handler.isEmpty());
}

View File

@ -20,8 +20,8 @@ import org.eclipse.jetty.http3.qpack.internal.parser.EncoderInstructionParser;
public class EncoderParserDebugHandler implements EncoderInstructionParser.Handler
{
public Queue<Integer> sectionAcknowledgements = new LinkedList<>();
public Queue<Integer> streamCancellations = new LinkedList<>();
public Queue<Long> sectionAcknowledgements = new LinkedList<>();
public Queue<Long> streamCancellations = new LinkedList<>();
public Queue<Integer> insertCountIncrements = new LinkedList<>();
private final QpackEncoder _encoder;
@ -37,7 +37,7 @@ public class EncoderParserDebugHandler implements EncoderInstructionParser.Handl
}
@Override
public void onSectionAcknowledgement(int streamId) throws QpackException
public void onSectionAcknowledgement(long streamId) throws QpackException
{
sectionAcknowledgements.add(streamId);
if (_encoder != null)
@ -45,7 +45,7 @@ public class EncoderParserDebugHandler implements EncoderInstructionParser.Handl
}
@Override
public void onStreamCancellation(int streamId) throws QpackException
public void onStreamCancellation(long streamId) throws QpackException
{
streamCancellations.add(streamId);
if (_encoder != null)

View File

@ -50,10 +50,6 @@ public class EvictionTest
return false;
}
};
// Set the instruction bytes to be passed on to the remote Encoder/Decoder through the handler directly.
_encoderHandler.setDecoder(_decoder);
_decoderHandler.setEncoder(_encoder);
}
@Test
@ -65,10 +61,14 @@ public class EvictionTest
for (int i = 0; i < 10000; i++)
{
HttpFields httpFields = newRandomFields(5);
int streamId = getPositiveInt(10);
long streamId = getPositiveInt(10);
_encoder.encode(encodedFields, streamId, new MetaData(HttpVersion.HTTP_3, httpFields));
_decoder.parseInstructionBuffer(_encoderHandler.getInstructionBuffer());
_decoder.decode(streamId, encodedFields, _decoderHandler);
_encoder.parseInstructionBuffer(_decoderHandler.getInstructionBuffer());
MetaData result = _decoderHandler.getMetaData();
assertNotNull(result);

View File

@ -38,10 +38,10 @@ public class NBitIntegerParserTest
ByteBuffer buffer2 = BufferUtil.toBuffer(bytes, 2, 1);
parser.setPrefix(7);
int value = parser.decode(buffer1);
int value = parser.decodeInt(buffer1);
assertThat(value, is(-1));
value = parser.decode(buffer2);
value = parser.decodeInt(buffer2);
assertThat(value, is(1337));
}
}

View File

@ -15,8 +15,8 @@ package org.eclipse.jetty.http3.qpack;
import java.nio.ByteBuffer;
import org.eclipse.jetty.http3.qpack.internal.util.NBitIntegerEncoder;
import org.eclipse.jetty.http3.qpack.internal.util.NBitIntegerParser;
import org.eclipse.jetty.http3.qpack.internal.util.NBitLongEncoder;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.TypeUtil;
import org.junit.jupiter.api.Test;
@ -31,17 +31,17 @@ public class NBitIntegerTest
@Test
public void testOctetsNeeded()
{
assertEquals(0, NBitLongEncoder.octectsNeeded(5, 10));
assertEquals(2, NBitLongEncoder.octectsNeeded(5, 1337));
assertEquals(1, NBitLongEncoder.octectsNeeded(8, 42));
assertEquals(3, NBitLongEncoder.octectsNeeded(8, 1337));
assertEquals(0, NBitIntegerEncoder.octectsNeeded(5, 10));
assertEquals(2, NBitIntegerEncoder.octectsNeeded(5, 1337));
assertEquals(1, NBitIntegerEncoder.octectsNeeded(8, 42));
assertEquals(3, NBitIntegerEncoder.octectsNeeded(8, 1337));
assertEquals(0, NBitLongEncoder.octectsNeeded(6, 62));
assertEquals(1, NBitLongEncoder.octectsNeeded(6, 63));
assertEquals(1, NBitLongEncoder.octectsNeeded(6, 64));
assertEquals(2, NBitLongEncoder.octectsNeeded(6, 63 + 0x00 + 0x80 * 0x01));
assertEquals(3, NBitLongEncoder.octectsNeeded(6, 63 + 0x00 + 0x80 * 0x80));
assertEquals(4, NBitLongEncoder.octectsNeeded(6, 63 + 0x00 + 0x80 * 0x80 * 0x80));
assertEquals(0, NBitIntegerEncoder.octectsNeeded(6, 62));
assertEquals(1, NBitIntegerEncoder.octectsNeeded(6, 63));
assertEquals(1, NBitIntegerEncoder.octectsNeeded(6, 64));
assertEquals(2, NBitIntegerEncoder.octectsNeeded(6, 63 + 0x00 + 0x80 * 0x01));
assertEquals(3, NBitIntegerEncoder.octectsNeeded(6, 63 + 0x00 + 0x80 * 0x80));
assertEquals(4, NBitIntegerEncoder.octectsNeeded(6, 63 + 0x00 + 0x80 * 0x80 * 0x80));
}
@Test
@ -82,12 +82,12 @@ public class NBitIntegerTest
int p = BufferUtil.flipToFill(buf);
if (n < 8)
buf.put((byte)0x00);
NBitLongEncoder.encode(buf, n, i);
NBitIntegerEncoder.encode(buf, n, i);
BufferUtil.flipToFlush(buf, p);
String r = TypeUtil.toHexString(BufferUtil.toArray(buf));
assertEquals(expected, r);
assertEquals(expected.length() / 2, (n < 8 ? 1 : 0) + NBitLongEncoder.octectsNeeded(n, i));
assertEquals(expected.length() / 2, (n < 8 ? 1 : 0) + NBitIntegerEncoder.octectsNeeded(n, i));
}
@Test
@ -126,7 +126,7 @@ public class NBitIntegerTest
{
ByteBuffer buf = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
_parser.setPrefix(n);
assertEquals(expected, _parser.decode(buf));
assertEquals(expected, _parser.decodeInt(buf));
}
@Test
@ -136,7 +136,7 @@ public class NBitIntegerTest
int p = BufferUtil.flipToFill(buf);
buf.put((byte)0x77);
buf.put((byte)0xFF);
NBitLongEncoder.encode(buf, 5, 10);
NBitIntegerEncoder.encode(buf, 5, 10);
BufferUtil.flipToFlush(buf, p);
String r = TypeUtil.toHexString(BufferUtil.toArray(buf));
@ -150,7 +150,7 @@ public class NBitIntegerTest
ByteBuffer buf = ByteBuffer.wrap(TypeUtil.fromHexString("77EaFF"));
buf.position(1);
_parser.setPrefix(5);
assertEquals(10, _parser.decode(buf));
assertEquals(10, _parser.decodeInt(buf));
}
@Test
@ -160,7 +160,7 @@ public class NBitIntegerTest
int p = BufferUtil.flipToFill(buf);
buf.put((byte)0x88);
buf.put((byte)0x00);
NBitLongEncoder.encode(buf, 5, 1337);
NBitIntegerEncoder.encode(buf, 5, 1337);
BufferUtil.flipToFlush(buf, p);
String r = TypeUtil.toHexString(BufferUtil.toArray(buf));
@ -174,7 +174,7 @@ public class NBitIntegerTest
ByteBuffer buf = ByteBuffer.wrap(TypeUtil.fromHexString("881f9a0aff"));
buf.position(1);
_parser.setPrefix(5);
assertEquals(1337, _parser.decode(buf));
assertEquals(1337, _parser.decodeInt(buf));
}
@Test
@ -184,7 +184,7 @@ public class NBitIntegerTest
int p = BufferUtil.flipToFill(buf);
buf.put((byte)0x88);
buf.put((byte)0xFF);
NBitLongEncoder.encode(buf, 8, 42);
NBitIntegerEncoder.encode(buf, 8, 42);
BufferUtil.flipToFlush(buf, p);
String r = TypeUtil.toHexString(BufferUtil.toArray(buf));
@ -198,6 +198,6 @@ public class NBitIntegerTest
ByteBuffer buf = ByteBuffer.wrap(TypeUtil.fromHexString("882aFf"));
buf.position(1);
_parser.setPrefix(8);
assertEquals(42, _parser.decode(buf));
assertEquals(42, _parser.decodeInt(buf));
}
}

View File

@ -35,10 +35,15 @@ public class QpackTestUtil
public static ByteBuffer toBuffer(List<Instruction> instructions)
{
ByteBufferPool.Lease lease = new ByteBufferPool.Lease(new NullByteBufferPool());
NullByteBufferPool bufferPool = new NullByteBufferPool();
ByteBufferPool.Lease lease = new ByteBufferPool.Lease(bufferPool);
instructions.forEach(i -> i.encode(lease));
assertThat(lease.getSize(), is(instructions.size()));
return lease.getByteBuffers().get(0);
ByteBuffer combinedBuffer = BufferUtil.allocate(Math.toIntExact(lease.getTotalLength()), false);
BufferUtil.clearToFill(combinedBuffer);
lease.getByteBuffers().forEach(combinedBuffer::put);
BufferUtil.flipToFlush(combinedBuffer, 0);
return combinedBuffer;
}
public static ByteBuffer hexToBuffer(String hexString)

View File

@ -13,22 +13,16 @@
package org.eclipse.jetty.http3.qpack;
import java.nio.ByteBuffer;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import org.eclipse.jetty.http.MetaData;
public class TestDecoderHandler implements QpackDecoder.Handler, Instruction.Handler
{
private final Queue<MetaData> _metadataList = new LinkedList<>();
private final Queue<Instruction> _instructionList = new LinkedList<>();
private QpackEncoder _encoder;
public void setEncoder(QpackEncoder encoder)
{
_encoder = encoder;
}
private final LinkedList<MetaData> _metadataList = new LinkedList<>();
private final LinkedList<Instruction> _instructionList = new LinkedList<>();
@Override
public void onMetaData(long streamId, MetaData metadata)
@ -37,11 +31,16 @@ public class TestDecoderHandler implements QpackDecoder.Handler, Instruction.Han
}
@Override
public void onInstructions(List<Instruction> instructions) throws QpackException
public void onInstructions(List<Instruction> instructions)
{
_instructionList.addAll(instructions);
if (_encoder != null)
_encoder.parseInstruction(QpackTestUtil.toBuffer(instructions));
}
public ByteBuffer getInstructionBuffer()
{
ByteBuffer byteBuffer = QpackTestUtil.toBuffer(_instructionList);
_instructionList.clear();
return byteBuffer;
}
public MetaData getMetaData()

View File

@ -13,26 +13,25 @@
package org.eclipse.jetty.http3.qpack;
import java.nio.ByteBuffer;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
public class TestEncoderHandler implements Instruction.Handler
{
private final Queue<Instruction> _instructionList = new LinkedList<>();
private QpackDecoder _decoder;
public void setDecoder(QpackDecoder decoder)
{
_decoder = decoder;
}
private final LinkedList<Instruction> _instructionList = new LinkedList<>();
@Override
public void onInstructions(List<Instruction> instructions) throws QpackException
public void onInstructions(List<Instruction> instructions)
{
_instructionList.addAll(instructions);
if (_decoder != null)
_decoder.parseInstruction(QpackTestUtil.toBuffer(instructions));
}
public ByteBuffer getInstructionBuffer()
{
ByteBuffer byteBuffer = QpackTestUtil.toBuffer(_instructionList);
_instructionList.clear();
return byteBuffer;
}
public Instruction getInstruction()

View File

@ -171,7 +171,7 @@ public abstract class QuicSession
// the remote address may change so store it again.
this.remoteAddress = remoteAddress;
quicheConnection.feedCipherText(cipherBufferIn);
quicheConnection.feedCipherText(cipherBufferIn, remoteAddress);
if (quicheConnection.isConnectionEstablished())
{

View File

@ -15,6 +15,7 @@ package org.eclipse.jetty.quic.quiche;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
@ -24,6 +25,7 @@ import java.util.List;
import org.eclipse.jetty.quic.quiche.ffi.LibQuiche;
import org.eclipse.jetty.quic.quiche.ffi.bool_pointer;
import org.eclipse.jetty.quic.quiche.ffi.char_pointer;
import org.eclipse.jetty.quic.quiche.ffi.netinet_h;
import org.eclipse.jetty.quic.quiche.ffi.size_t;
import org.eclipse.jetty.quic.quiche.ffi.size_t_pointer;
import org.eclipse.jetty.quic.quiche.ffi.ssize_t;
@ -66,7 +68,9 @@ public class QuicheConnection
byte[] scid = new byte[connectionIdLength];
SECURE_RANDOM.nextBytes(scid);
LibQuiche.quiche_config libQuicheConfig = buildConfig(quicheConfig);
LibQuiche.quiche_conn quicheConn = LibQuiche.INSTANCE.quiche_connect(peer.getHostName(), scid, new size_t(scid.length), libQuicheConfig);
netinet_h.sockaddr_in to = netinet_h.to_sock_addr(peer);
LibQuiche.quiche_conn quicheConn = LibQuiche.INSTANCE.quiche_connect(peer.getHostName(), scid, new size_t(scid.length), to, new size_t(to.size()), libQuicheConfig);
return new QuicheConnection(quicheConn, libQuicheConfig);
}
@ -245,7 +249,7 @@ public class QuicheConnection
* Fully consumes the {@code packetRead} buffer if the connection was accepted.
* @return an established connection if accept succeeded, null if accept failed and negotiation should be tried.
*/
public static QuicheConnection tryAccept(QuicheConfig quicheConfig, TokenValidator tokenValidator, ByteBuffer packetRead) throws IOException
public static QuicheConnection tryAccept(QuicheConfig quicheConfig, TokenValidator tokenValidator, ByteBuffer packetRead, SocketAddress peer) throws IOException
{
uint8_t_pointer type = new uint8_t_pointer();
uint32_t_pointer version = new uint32_t_pointer();
@ -297,7 +301,9 @@ public class QuicheConnection
LOG.debug(" connection creation...");
LibQuiche.quiche_config libQuicheConfig = buildConfig(quicheConfig);
LibQuiche.quiche_conn quicheConn = LibQuiche.INSTANCE.quiche_accept(dcid, dcid_len.getPointee(), odcid, new size_t(odcid.length), libQuicheConfig);
netinet_h.sockaddr_in from = netinet_h.to_sock_addr(peer);
LibQuiche.quiche_conn quicheConn = LibQuiche.INSTANCE.quiche_accept(dcid, dcid_len.getPointee(), odcid, new size_t(odcid.length), from, new size_t(from.size()), libQuicheConfig);
if (quicheConn == null)
{
LibQuiche.INSTANCE.quiche_config_free(libQuicheConfig);
@ -309,7 +315,7 @@ public class QuicheConnection
LOG.debug("accepted, immediately receiving the same packet - remaining in buffer: {}", packetRead.remaining());
while (packetRead.hasRemaining())
{
quicheConnection.feedCipherText(packetRead);
quicheConnection.feedCipherText(packetRead, peer);
}
return quicheConnection;
}
@ -345,15 +351,21 @@ public class QuicheConnection
/**
* Read the buffer of cipher text coming from the network.
* @param buffer the buffer to read.
* @param peer
* @return how many bytes were consumed.
* @throws IOException
*/
public synchronized int feedCipherText(ByteBuffer buffer) throws IOException
public synchronized int feedCipherText(ByteBuffer buffer, SocketAddress peer) throws IOException
{
if (quicheConn == null)
throw new IOException("Cannot receive when not connected");
int received = libQuiche().quiche_conn_recv(quicheConn, buffer, new size_t(buffer.remaining())).intValue();
LibQuiche.quiche_recv_info info = new LibQuiche.quiche_recv_info();
netinet_h.sockaddr_in from = netinet_h.to_sock_addr(peer);
info.from = netinet_h.sockaddr_in.byReference(from);
info.from_len = new size_t(from.size());
int received = libQuiche().quiche_conn_recv(quicheConn, buffer, new size_t(buffer.remaining()), info).intValue();
if (received < 0)
throw new IOException("Quiche failed to receive packet; err=" + LibQuiche.quiche_error.errToString(received));
buffer.position(buffer.position() + received);
@ -370,7 +382,12 @@ public class QuicheConnection
{
if (quicheConn == null)
throw new IOException("Cannot send when not connected");
int written = libQuiche().quiche_conn_send(quicheConn, buffer, new size_t(buffer.remaining())).intValue();
LibQuiche.quiche_send_info quiche_send_info = new LibQuiche.quiche_send_info();
quiche_send_info.to = new netinet_h.sockaddr_storage();
quiche_send_info.to_len = new size_t(quiche_send_info.to.size());
int written = libQuiche().quiche_conn_send(quicheConn, buffer, new size_t(buffer.remaining()), quiche_send_info).intValue();
if (written == LibQuiche.quiche_error.QUICHE_ERR_DONE)
return 0;
if (written < 0L)

View File

@ -28,7 +28,7 @@ public interface LibQuiche extends Library
{
// This interface is a translation of the quiche.h header of a specific version.
// It needs to be reviewed each time the native lib version changes.
String EXPECTED_QUICHE_VERSION = "0.7.0";
String EXPECTED_QUICHE_VERSION = "0.9.0";
// load the native lib
LibQuiche INSTANCE = Native.load("quiche", LibQuiche.class);
@ -56,7 +56,7 @@ public interface LibQuiche extends Library
// QUIC transport API.
// The current QUIC wire version.
int QUICHE_PROTOCOL_VERSION = 0xff00001d;
int QUICHE_PROTOCOL_VERSION = 0x00000001;
// The maximum length of a connection ID.
int QUICHE_MAX_CONN_ID_LEN = 20;
@ -251,7 +251,7 @@ public interface LibQuiche extends Library
int quiche_enable_debug_logging(LoggingCallback cb, Pointer argp);
// Creates a new client-side connection.
quiche_conn quiche_connect(String server_name, byte[] scid, size_t scid_len, quiche_config config);
quiche_conn quiche_connect(String server_name, byte[] scid, size_t scid_len, netinet_h.sockaddr_in to, size_t to_len, quiche_config config);
interface packet_type
{
@ -304,7 +304,7 @@ public interface LibQuiche extends Library
uint32_t version, ByteBuffer out, size_t out_len);
// Creates a new server-side connection.
quiche_conn quiche_accept(byte[] scid, size_t scid_len, byte[] odcid, size_t odcid_len, quiche_config config);
quiche_conn quiche_accept(byte[] scid, size_t scid_len, byte[] odcid, size_t odcid_len, netinet_h.sockaddr_in from, size_t from_len, quiche_config config);
// Returns the amount of time until the next timeout event, in milliseconds.
uint64_t quiche_conn_timeout_as_millis(quiche_conn conn);
@ -315,11 +315,33 @@ public interface LibQuiche extends Library
// Collects and returns statistics about the connection.
void quiche_conn_stats(quiche_conn conn, quiche_stats out);
@Structure.FieldOrder({"to", "to_len", "at"})
class quiche_send_info extends Structure
{
public netinet_h.sockaddr_storage to;
public size_t to_len;
public timespec at;
}
@Structure.FieldOrder({"tv_sec", "tv_nsec"})
class timespec extends Structure
{
public uint64_t tv_sec;
public long tv_nsec;
}
// Writes a single QUIC packet to be sent to the peer.
ssize_t quiche_conn_send(quiche_conn conn, ByteBuffer out, size_t out_len);
ssize_t quiche_conn_send(quiche_conn conn, ByteBuffer out, size_t out_len, quiche_send_info out_info);
@Structure.FieldOrder({"from", "from_len"})
class quiche_recv_info extends Structure
{
public netinet_h.sockaddr_in.ByReference from;
public size_t from_len;
}
// Processes QUIC packets received from the peer.
ssize_t quiche_conn_recv(quiche_conn conn, ByteBuffer buf, size_t buf_len);
ssize_t quiche_conn_recv(quiche_conn conn, ByteBuffer buf, size_t buf_len, quiche_recv_info info);
// Returns the negotiated ALPN protocol.
void quiche_conn_application_proto(quiche_conn conn, char_pointer out, size_t_pointer out_len);

View File

@ -0,0 +1,101 @@
//
// ========================================================================
// 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.quic.quiche.ffi;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
public interface netinet_h
{
uint16_t AF_INET = new uint16_t(2);
static sockaddr_in to_sock_addr(SocketAddress socketAddress)
{
if (!(socketAddress instanceof InetSocketAddress))
throw new IllegalArgumentException("Expected InetSocketAddress instance, got: " + socketAddress);
InetSocketAddress inetSocketAddress = (InetSocketAddress)socketAddress;
InetAddress address = inetSocketAddress.getAddress();
if (!(address instanceof Inet4Address))
throw new UnsupportedOperationException("TODO: only ipv4 is supported for now");
sockaddr_in sa = new sockaddr_in();
sa.sin_family = AF_INET;
sa.sin_addr = new uint32_t(ByteBuffer.wrap(address.getAddress()).getInt()); // TODO: is the endianness correct?
sa.sin_port = new uint16_t(inetSocketAddress.getPort());
return sa;
}
@Structure.FieldOrder({"sa_family", "sa_data"})
class sockaddr extends Structure
{
public uint16_t sa_family;
public byte[] sa_data = new byte[14]; // 14 bytes of protocol address
}
@Structure.FieldOrder({"sin_family", "sin_port", "sin_addr", "sin_zero"})
class sockaddr_in extends Structure
{
public uint16_t sin_family;
public uint16_t sin_port;
public uint32_t sin_addr;
public byte[] sin_zero = new byte[8]; // padding to have the same size as sockaddr
public sockaddr_in()
{
}
private sockaddr_in(Pointer p)
{
super(p);
read();
}
public static ByReference byReference(sockaddr_in inet)
{
inet.write();
return new ByReference(inet.getPointer());
}
public static class ByReference extends sockaddr_in implements Structure.ByReference
{
private ByReference(Pointer p)
{
super(p);
}
}
}
@Structure.FieldOrder({"sin6_family", "sin6_port", "sin6_flowinfo", "s6_addr", "sin6_scope_id"})
class sockaddr_in6 extends Structure
{
public uint16_t sin6_family;
public uint16_t sin6_port;
public uint32_t sin6_flowinfo;
public byte[] s6_addr = new byte[16];
public uint32_t sin6_scope_id;
}
@Structure.FieldOrder({"ss_family", "ss_zero"})
class sockaddr_storage extends Structure
{
public uint16_t ss_family;
public byte[] ss_zero = new byte[126]; // padding
}
}

View File

@ -0,0 +1,28 @@
//
// ========================================================================
// 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.quic.quiche.ffi;
import com.sun.jna.IntegerType;
public class uint16_t extends IntegerType
{
public uint16_t()
{
this(0);
}
public uint16_t(int v)
{
super(2, v, true);
}
}

View File

@ -1,6 +1,6 @@
git clone --recursive https://github.com/cloudflare/quiche
cd quiche
git checkout -b tag-0.7.0 tags/0.7.0
git checkout -b tag-0.9.0 tags/0.9.0
cargo build --features ffi
ls ./target/debug/libquiche.so

View File

@ -64,7 +64,7 @@ public class ServerQuicConnection extends QuicConnection
{
ByteBufferPool byteBufferPool = getByteBufferPool();
// TODO make the token validator configurable
QuicheConnection quicheConnection = QuicheConnection.tryAccept(quicheConfig, new SimpleTokenValidator((InetSocketAddress)remoteAddress), cipherBuffer);
QuicheConnection quicheConnection = QuicheConnection.tryAccept(quicheConfig, new SimpleTokenValidator((InetSocketAddress)remoteAddress), cipherBuffer, remoteAddress);
if (quicheConnection == null)
{
// TODO make the buffer size configurable