diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/PreEncodedHttpField.java b/jetty-http/src/main/java/org/eclipse/jetty/http/PreEncodedHttpField.java index adb7fd972a8..2079bdcda0a 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/PreEncodedHttpField.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/PreEncodedHttpField.java @@ -112,4 +112,9 @@ public class PreEncodedHttpField extends HttpField { bufferInFillMode.put(_encodedField[index(version)]); } + + public int getEncodedLength(HttpVersion version) + { + return _encodedField[index(version)].length; + } } diff --git a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/EncodableEntry.java b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/EncodableEntry.java index f67320d660a..6d4d035383b 100644 --- a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/EncodableEntry.java +++ b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/EncodableEntry.java @@ -17,61 +17,92 @@ import java.nio.ByteBuffer; import java.util.Objects; import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.http.PreEncodedHttpField; import org.eclipse.jetty.http3.qpack.table.Entry; -class EncodableEntry +abstract class EncodableEntry { - private final Entry _referencedEntry; - private final Entry _referencedName; - private final HttpField _field; - private final boolean _huffman; - - public EncodableEntry(Entry entry) + static EncodableEntry getReferencedEntry(Entry entry) { - // We want to reference the entry directly. - _referencedEntry = entry; - _referencedName = null; - _field = null; - _huffman = false; + return new ReferencedEntry(entry); } - public EncodableEntry(Entry nameEntry, HttpField field, boolean huffman) + static EncodableEntry getNameReferencedEntry(Entry nameEntry, HttpField field, boolean huffman) { - // We want to reference the name and use a literal value. - _referencedEntry = null; - _referencedName = nameEntry; - _field = field; - _huffman = huffman; + return new ReferencedNameEntry(nameEntry, field, huffman); } - public EncodableEntry(HttpField field, boolean huffman) + static EncodableEntry getLiteralEntry(HttpField field, boolean huffman) { - // We want to use a literal name and value. - _referencedEntry = null; - _referencedName = null; - _field = field; - _huffman = huffman; + return new LiteralEntry(field, huffman); } - public void encode(ByteBuffer buffer, int base) + static EncodableEntry getPreEncodedEntry(PreEncodedHttpField httpField) { - // TODO: When should we ever encode with post-base indexes? - // We are currently always using the base as the start of the current dynamic table entries. - if (_referencedEntry != null) + return new PreEncodedEntry(httpField); + } + + abstract void encode(ByteBuffer buffer, int base); + + abstract int getRequiredSize(int base); + + abstract int getRequiredInsertCount(); + + private static class ReferencedEntry extends EncodableEntry + { + private final Entry _entry; + + public ReferencedEntry(Entry entry) { - byte staticBit = _referencedEntry.isStatic() ? (byte)0x40 : (byte)0x00; + _entry = entry; + } + + @Override + public void encode(ByteBuffer buffer, int base) + { + byte staticBit = _entry.isStatic() ? (byte)0x40 : (byte)0x00; buffer.put((byte)(0x80 | staticBit)); - int relativeIndex = _referencedEntry.getIndex() - base; + int relativeIndex = _entry.getIndex() - base; NBitInteger.encode(buffer, 6, relativeIndex); } - else if (_referencedName != null) + + @Override + public int getRequiredSize(int base) + { + int relativeIndex = _entry.getIndex() - base; + return 1 + NBitInteger.octectsNeeded(6, relativeIndex); + } + + @Override + public int getRequiredInsertCount() + { + return _entry.isStatic() ? 0 : _entry.getIndex() + 1; + } + } + + private static class ReferencedNameEntry extends EncodableEntry + { + private final Entry _nameEntry; + private final HttpField _field; + private final boolean _huffman; + + public ReferencedNameEntry(Entry nameEntry, HttpField field, boolean huffman) + { + _nameEntry = nameEntry; + _field = field; + _huffman = huffman; + } + + @Override + public void encode(ByteBuffer buffer, int base) { byte allowIntermediary = 0x00; // TODO: this is 0x20 bit, when should this be set? - byte staticBit = _referencedName.isStatic() ? (byte)0x10 : (byte)0x00; + byte staticBit = _nameEntry.isStatic() ? (byte)0x10 : (byte)0x00; // Encode the prefix. buffer.put((byte)(0x40 | allowIntermediary | staticBit)); - int relativeIndex = _referencedName.getIndex() - base; + int relativeIndex = _nameEntry.getIndex() - base; NBitInteger.encode(buffer, 4, relativeIndex); // Encode the value. @@ -89,7 +120,42 @@ class EncodableEntry buffer.put(value.getBytes()); } } - else + + @Override + public int getRequiredSize(int base) + { + String value = getValue(); + int relativeIndex = _nameEntry.getIndex() - base; + int valueLength = _huffman ? Huffman.octetsNeeded(value) : value.length(); + return 1 + NBitInteger.octectsNeeded(4, relativeIndex) + 1 + NBitInteger.octectsNeeded(7, valueLength) + valueLength; + } + + @Override + public int getRequiredInsertCount() + { + return _nameEntry.isStatic() ? 0 : _nameEntry.getIndex() + 1; + } + + private String getValue() + { + String value = Objects.requireNonNull(_field).getValue(); + return (value == null) ? "" : value; + } + } + + private static class LiteralEntry extends EncodableEntry + { + private final HttpField _field; + private final boolean _huffman; + + public LiteralEntry(HttpField field, boolean huffman) + { + _field = field; + _huffman = huffman; + } + + @Override + public void encode(ByteBuffer buffer, int base) { byte allowIntermediary = 0x00; // TODO: this is 0x10 bit, when should this be set? String name = getName(); @@ -116,23 +182,9 @@ class EncodableEntry buffer.put(value.getBytes()); } } - } - public int getRequiredSize(int base) - { - if (_referencedEntry != null) - { - int relativeIndex = _referencedEntry.getIndex() - base; - return 1 + NBitInteger.octectsNeeded(6, relativeIndex); - } - else if (_referencedName != null) - { - String value = getValue(); - int relativeIndex = _referencedName.getIndex() - base; - int valueLength = _huffman ? Huffman.octetsNeeded(value) : value.length(); - return 1 + NBitInteger.octectsNeeded(4, relativeIndex) + 1 + NBitInteger.octectsNeeded(7, valueLength) + valueLength; - } - else + @Override + public int getRequiredSize(int base) { String name = getName(); String value = getValue(); @@ -140,27 +192,51 @@ class EncodableEntry int valueLength = _huffman ? Huffman.octetsNeeded(value) : value.length(); return 2 + NBitInteger.octectsNeeded(3, nameLength) + nameLength + NBitInteger.octectsNeeded(7, valueLength) + valueLength; } - } - private String getName() - { - String name = Objects.requireNonNull(_field).getName(); - return (name == null) ? "" : name; - } - - private String getValue() - { - String value = Objects.requireNonNull(_field).getValue(); - return (value == null) ? "" : value; - } - - public int getRequiredInsertCount() - { - if (_referencedEntry != null && !_referencedEntry.isStatic()) - return _referencedEntry.getIndex() + 1; - else if (_referencedName != null && !_referencedName.isStatic()) - return _referencedName.getIndex() + 1; - else + @Override + public int getRequiredInsertCount() + { return 0; + } + + private String getName() + { + String name = Objects.requireNonNull(_field).getName(); + return (name == null) ? "" : name; + } + + private String getValue() + { + String value = Objects.requireNonNull(_field).getValue(); + return (value == null) ? "" : value; + } + } + + private static class PreEncodedEntry extends EncodableEntry + { + private final PreEncodedHttpField _httpField; + + public PreEncodedEntry(PreEncodedHttpField httpField) + { + _httpField = httpField; + } + + @Override + public void encode(ByteBuffer buffer, int base) + { + _httpField.putTo(buffer, HttpVersion.HTTP_3); + } + + @Override + public int getRequiredSize(int base) + { + return _httpField.getEncodedLength(HttpVersion.HTTP_3); + } + + @Override + public int getRequiredInsertCount() + { + return 0; + } } } diff --git a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackEncoder.java b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackEncoder.java index 4b30efc91f4..e78b7da9ba2 100644 --- a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackEncoder.java +++ b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackEncoder.java @@ -317,16 +317,17 @@ public class QpackEncoder if (field.getValue() == null) field = new HttpField(field.getHeader(), field.getName(), ""); - // TODO: - // 1. The field.getHeader() could be null. - // 3. Handle pre-encoded HttpFields. + // TODO: The field.getHeader() could be null. + + if (field instanceof PreEncodedHttpField) + return EncodableEntry.getPreEncodedEntry((PreEncodedHttpField)field); boolean canCreateEntry = shouldIndex(field) && (Entry.getSize(field) <= dynamicTable.getSpace()); Entry entry = _context.get(field); if (entry != null && referenceEntry(entry, streamInfo)) { - return new EncodableEntry(entry); + return EncodableEntry.getReferencedEntry(entry); } else { @@ -339,7 +340,7 @@ public class QpackEncoder // Should we reference this entry and risk blocking. if (referenceEntry(newEntry, streamInfo)) - return new EncodableEntry(newEntry); + return EncodableEntry.getReferencedEntry(newEntry); } } @@ -356,10 +357,10 @@ public class QpackEncoder // Should we reference this entry and risk blocking. if (referenceEntry(newEntry, streamInfo)) - return new EncodableEntry(newEntry); + return EncodableEntry.getReferencedEntry(newEntry); } - return new EncodableEntry(nameEntry, field, huffman); + return EncodableEntry.getNameReferencedEntry(nameEntry, field, huffman); } else { @@ -371,10 +372,10 @@ public class QpackEncoder // Should we reference this entry and risk blocking. if (referenceEntry(newEntry, streamInfo)) - return new EncodableEntry(newEntry); + return EncodableEntry.getReferencedEntry(newEntry); } - return new EncodableEntry(field, huffman); + return EncodableEntry.getLiteralEntry(field, huffman); } } @@ -390,7 +391,7 @@ public class QpackEncoder boolean canCreateEntry = shouldIndex(field) && (Entry.getSize(field) <= dynamicTable.getSpace()); if (!canCreateEntry) - return false; + return false; Entry newEntry = new Entry(field); dynamicTable.add(newEntry); diff --git a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackFieldPreEncoder.java b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackFieldPreEncoder.java index 6e0a6d9275a..fd7ef5e12f1 100644 --- a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackFieldPreEncoder.java +++ b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackFieldPreEncoder.java @@ -42,25 +42,25 @@ public class QpackFieldPreEncoder implements HttpFieldPreEncoder boolean huffman = !QpackEncoder.DO_NOT_HUFFMAN.contains(header); if (notIndexed) { - encodableEntry = new EncodableEntry(httpField, huffman); + encodableEntry = EncodableEntry.getLiteralEntry(httpField, huffman); } else { Entry entry = staticTable.get(httpField); if (entry != null) { - encodableEntry = new EncodableEntry(entry); + encodableEntry = EncodableEntry.getReferencedEntry(entry); } else { Entry nameEntry = staticTable.get(name); if (nameEntry != null) { - encodableEntry = new EncodableEntry(nameEntry, httpField, huffman); + encodableEntry = EncodableEntry.getNameReferencedEntry(nameEntry, httpField, huffman); } else { - encodableEntry = new EncodableEntry(httpField, huffman); + encodableEntry = EncodableEntry.getLiteralEntry(httpField, huffman); } } }