Issue #11420 - fix dynamic table referencing in QpackDecoder

Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
This commit is contained in:
Lachlan Roberts 2024-04-11 13:01:30 +10:00
parent 83526f77d3
commit 600dbfee25
6 changed files with 145 additions and 13 deletions

View File

@ -379,7 +379,7 @@ public class QpackDecoder implements Dumpable
LOG.debug("Duplicate: index={}", index);
DynamicTable dynamicTable = _context.getDynamicTable();
Entry referencedEntry = dynamicTable.get(index);
Entry referencedEntry = dynamicTable.getRelative(index);
// Add the new Entry to the DynamicTable.
Entry entry = new Entry(referencedEntry.getHttpField());
@ -396,7 +396,7 @@ public class QpackDecoder implements Dumpable
StaticTable staticTable = QpackContext.getStaticTable();
DynamicTable dynamicTable = _context.getDynamicTable();
Entry referencedEntry = isDynamicTableIndex ? dynamicTable.get(nameIndex) : staticTable.get(nameIndex);
Entry referencedEntry = isDynamicTableIndex ? dynamicTable.getRelative(nameIndex) : staticTable.get(nameIndex);
// Add the new Entry to the DynamicTable.
Entry entry = new Entry(new HttpField(referencedEntry.getHttpField().getHeader(), referencedEntry.getHttpField().getName(), value));

View File

@ -68,14 +68,14 @@ public abstract class EncodableEntry
{
// Indexed Field Line with Static Reference.
buffer.put((byte)(0x80 | 0x40));
int relativeIndex = _entry.getIndex();
NBitIntegerEncoder.encode(buffer, 6, relativeIndex);
int index = _entry.getIndex();
NBitIntegerEncoder.encode(buffer, 6, index);
}
else if (_entry.getIndex() < base)
{
// Indexed Field Line with Dynamic Reference.
buffer.put((byte)0x80);
int relativeIndex = base - (_entry.getIndex() + 1);
int relativeIndex = (base - 1) - _entry.getIndex();
NBitIntegerEncoder.encode(buffer, 6, relativeIndex);
}
else
@ -100,7 +100,7 @@ public abstract class EncodableEntry
else if (_entry.getIndex() < base)
{
// Indexed Field Line with Dynamic Reference.
int relativeIndex = base - (_entry.getIndex() + 1);
int relativeIndex = (base - 1) - _entry.getIndex();
return NBitIntegerEncoder.octetsNeeded(6, relativeIndex);
}
else
@ -150,7 +150,7 @@ public abstract class EncodableEntry
{
// Literal Field Line with Dynamic Name Reference.
buffer.put((byte)(0x40 | (allowIntermediary ? 0x20 : 0x00)));
int relativeIndex = base - (_nameEntry.getIndex() + 1);
int relativeIndex = (base - 1) - _nameEntry.getIndex();
NBitIntegerEncoder.encode(buffer, 4, relativeIndex);
}
else

View File

@ -66,6 +66,7 @@ public class QpackContext
return _dynamicTable.get(StringUtil.asciiToLowerCase(name));
}
@Deprecated
public Entry get(int index)
{
if (index <= StaticTable.STATIC_SIZE)

View File

@ -229,7 +229,7 @@ public class EncodedFieldSection
public HttpField decode(QpackContext context)
{
if (_dynamicTable)
return context.getDynamicTable().getAbsolute(_base - (_index + 1)).getHttpField();
return context.getDynamicTable().getRelative(_index, _base).getHttpField();
else
return QpackContext.getStaticTable().get(_index).getHttpField();
}
@ -247,7 +247,7 @@ public class EncodedFieldSection
@Override
public HttpField decode(QpackContext context)
{
return context.getDynamicTable().getAbsolute(_base + _index).getHttpField();
return context.getDynamicTable().getPostBase(_index, _base).getHttpField();
}
}
@ -272,7 +272,7 @@ public class EncodedFieldSection
{
HttpField field;
if (_dynamicTable)
field = context.getDynamicTable().getAbsolute(_base - (_nameIndex + 1)).getHttpField();
field = context.getDynamicTable().getRelative(_nameIndex, _base).getHttpField();
else
field = QpackContext.getStaticTable().get(_nameIndex).getHttpField();
@ -296,7 +296,7 @@ public class EncodedFieldSection
@Override
public HttpField decode(QpackContext context)
{
HttpField field = context.getDynamicTable().getAbsolute(_base + _nameIndex).getHttpField();
HttpField field = context.getDynamicTable().getPostBase(_nameIndex, _base).getHttpField();
return new HttpField(field.getHeader(), field.getName(), _value);
}
}

View File

@ -146,7 +146,7 @@ public class DynamicTable implements Iterable<Entry>, Dumpable
if (index < 0 || index >= _entries.size())
throw new IllegalArgumentException("Invalid Index " + index);
return _entries.get(index);
return get(index);
}
public Entry get(int index)
@ -164,9 +164,24 @@ public class DynamicTable implements Iterable<Entry>, Dumpable
return _fieldMap.get(field);
}
public Entry getRelative(int index)
{
return getRelative(index, getInsertCount());
}
public Entry getRelative(int index, int base)
{
return getAbsolute(base - 1 - index);
}
public Entry getPostBase(int index, int base)
{
return getAbsolute(base + index);
}
public int getBase()
{
if (_entries.size() == 0)
if (_entries.isEmpty())
return _absoluteIndex;
return _entries.get(0).getIndex();
}

View File

@ -0,0 +1,116 @@
//
// ========================================================================
// Copyright (c) 1995 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.http.MetaData;
import org.eclipse.jetty.http3.qpack.internal.table.Entry;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.StringUtil;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class QpackDecoderTest
{
private QpackDecoder _decoder;
private TestDecoderHandler _decoderHandler;
@BeforeEach
public void before()
{
_decoderHandler = new TestDecoderHandler();
_decoder = new QpackDecoder(_decoderHandler);
_decoder.setBeginNanoTimeSupplier(NanoTime::now);
}
@Test
public void testDynamicNameReference() throws Exception
{
_decoder.setMaxTableCapacity(2048);
QpackDecoder.InstructionHandler instructionHandler = _decoder.getInstructionHandler();
instructionHandler.onSetDynamicTableCapacity(2048);
instructionHandler.onInsertWithLiteralName("name0", "value0");
instructionHandler.onInsertWithLiteralName("name1", "value1");
instructionHandler.onInsertWithLiteralName("name2", "value2");
instructionHandler.onInsertWithLiteralName("name3", "value3");
instructionHandler.onInsertNameWithReference(5, false, "static0");
instructionHandler.onInsertWithLiteralName("name4", "value4");
instructionHandler.onInsertNameWithReference(2, true, "dynamic0");
instructionHandler.onDuplicate(6);
// Indexes into the static table are absolute.
Entry entry = _decoder.getQpackContext().getDynamicTable().get(4);
assertThat(entry.getHttpField().getName(), equalTo("cookie"));
assertThat(entry.getHttpField().getValue(), equalTo("static0"));
// Named reference is relative to the most recently inserted entry.
entry = _decoder.getQpackContext().getDynamicTable().get(6);
assertThat(entry.getHttpField().getName(), equalTo("name3"));
assertThat(entry.getHttpField().getValue(), equalTo("dynamic0"));
// Duplicate reference is relative to the most recently inserted entry.
entry = _decoder.getQpackContext().getDynamicTable().get(7);
assertThat(entry.getHttpField().getName(), equalTo("name0"));
assertThat(entry.getHttpField().getValue(), equalTo("value0"));
}
@Test
public void testDecodeRequest() throws Exception
{
_decoder.setMaxTableCapacity(2048);
QpackDecoder.InstructionHandler instructionHandler = _decoder.getInstructionHandler();
instructionHandler.onSetDynamicTableCapacity(2048);
instructionHandler.onInsertNameWithReference(0, false, "licensed.app");
instructionHandler.onInsertWithLiteralName("sec-ch-ua", "\"Not A(Brand\";v=\"99\", \"Brave\";v=\"121\", \"Chromium\";v=\"121\"");
instructionHandler.onInsertWithLiteralName("sec-ch-ua-mobile", "?0");
instructionHandler.onInsertWithLiteralName("sec-ch-ua-platform", "Windows");
instructionHandler.onInsertWithLiteralName("dnt", "1");
instructionHandler.onInsertNameWithReference(95, false, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36");
instructionHandler.onInsertNameWithReference(29, false, "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8");
instructionHandler.onInsertWithLiteralName("sec-gpc", "1");
instructionHandler.onInsertWithLiteralName("sec-fetch-site", "none");
instructionHandler.onInsertWithLiteralName("sec-fetch-mode", "navigate");
instructionHandler.onInsertWithLiteralName("sec-fetch-user", "?1");
instructionHandler.onInsertWithLiteralName("sec-fetch-dest", "value=document");
instructionHandler.onInsertNameWithReference(72, false, "en-US,en;q=0.9");
instructionHandler.onInsertNameWithReference(8, false, "2024-02-17T02:19:47.4433882Z");
instructionHandler.onInsertNameWithReference(1, false, "/login/GoogleLogin.js");
instructionHandler.onInsertNameWithReference(90, false, "https://licensed.app");
instructionHandler.onInsertNameWithReference(7, true, "same-origin");
instructionHandler.onInsertNameWithReference(7, true, "cors");
instructionHandler.onInsertNameWithReference(6, true, "script");
instructionHandler.onInsertNameWithReference(13, false, "https://licensed.app/");
assertTrue(_decoder.decode(0, fromHex("1500D193D78592848f918e90Dd8c83828180Df87"), _decoderHandler));
MetaData metaData = _decoderHandler.getMetaData();
// Check headers were correctly referenced from dynamic table.
assertThat(metaData.getHttpFields().get("sec-fetch-site"), equalTo("same-origin"));
assertThat(metaData.getHttpFields().get("sec-fetch-mode"), equalTo("cors"));
assertThat(metaData.getHttpFields().get("sec-fetch-dest"), equalTo("script"));
}
private ByteBuffer fromHex(String hex)
{
return BufferUtil.toBuffer(StringUtil.fromHexString(hex));
}
}