Issue #9554 - move common hpack/qpack code to jetty-http

Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
This commit is contained in:
Lachlan Roberts 2023-04-11 16:51:37 +10:00
parent 24b7d06fd5
commit ebec7d2ea3
37 changed files with 367 additions and 974 deletions

View File

@ -18,6 +18,7 @@ module org.eclipse.jetty.http
requires transitive org.eclipse.jetty.io; requires transitive org.eclipse.jetty.io;
exports org.eclipse.jetty.http; exports org.eclipse.jetty.http;
exports org.eclipse.jetty.http.compression;
exports org.eclipse.jetty.http.pathmap; exports org.eclipse.jetty.http.pathmap;
uses org.eclipse.jetty.http.HttpFieldPreEncoder; uses org.eclipse.jetty.http.HttpFieldPreEncoder;

View File

@ -11,7 +11,7 @@
// ======================================================================== // ========================================================================
// //
package org.eclipse.jetty.http3.qpack.internal.util; package org.eclipse.jetty.http.compression;
public class EncodingException extends Exception public class EncodingException extends Exception
{ {

View File

@ -11,14 +11,13 @@
// ======================================================================== // ========================================================================
// //
package org.eclipse.jetty.http2.hpack; package org.eclipse.jetty.http.compression;
import java.nio.ByteBuffer;
import org.eclipse.jetty.util.Utf8StringBuilder;
public class Huffman public class Huffman
{ {
private Huffman()
{
}
// Appendix C: Huffman Codes // Appendix C: Huffman Codes
// http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#appendix-C // http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#appendix-C
@ -286,7 +285,7 @@ public class Huffman
static final int[][] LCCODES = new int[CODES.length][]; static final int[][] LCCODES = new int[CODES.length][];
static final char EOS = 256; static final char EOS = 256;
// Huffman decode tree stored in a flattened char array for good // Huffman decode tree stored in a flattened char array for good
// locality of reference. // locality of reference.
static final char[] tree; static final char[] tree;
static final char[] rowsym; static final char[] rowsym;
@ -302,9 +301,9 @@ public class Huffman
} }
int r = 0; int r = 0;
for (int i = 0; i < CODES.length; i++) for (int[] ints : CODES)
{ {
r += (CODES[i][1] + 7) / 8; r += (ints[1] + 7) / 8;
} }
tree = new char[r * 256]; tree = new char[r * 256];
rowsym = new char[r]; rowsym = new char[r];
@ -347,200 +346,4 @@ public class Huffman
} }
} }
} }
public static String decode(ByteBuffer buffer) throws HpackException.CompressionException
{
return decode(buffer, buffer.remaining());
}
public static String decode(ByteBuffer buffer, int length) throws HpackException.CompressionException
{
Utf8StringBuilder utf8 = new Utf8StringBuilder(length * 2);
int node = 0;
int current = 0;
int bits = 0;
for (int i = 0; i < length; i++)
{
int b = buffer.get() & 0xFF;
current = (current << 8) | b;
bits += 8;
while (bits >= 8)
{
int c = (current >>> (bits - 8)) & 0xFF;
node = tree[node * 256 + c];
if (rowbits[node] != 0)
{
if (rowsym[node] == EOS)
throw new HpackException.CompressionException("EOS in content");
// terminal node
utf8.append((byte)(0xFF & rowsym[node]));
bits -= rowbits[node];
node = 0;
}
else
{
// non-terminal node
bits -= 8;
}
}
}
while (bits > 0)
{
int c = (current << (8 - bits)) & 0xFF;
int lastNode = node;
node = tree[node * 256 + c];
if (rowbits[node] == 0 || rowbits[node] > bits)
{
int requiredPadding = 0;
for (int i = 0; i < bits; i++)
{
requiredPadding = (requiredPadding << 1) | 1;
}
if ((c >> (8 - bits)) != requiredPadding)
throw new HpackException.CompressionException("Incorrect padding");
node = lastNode;
break;
}
utf8.append((byte)(0xFF & rowsym[node]));
bits -= rowbits[node];
node = 0;
}
if (node != 0)
throw new HpackException.CompressionException("Bad termination");
return utf8.toString();
}
public static int octetsNeeded(String s)
{
return octetsNeeded(CODES, s);
}
public static int octetsNeeded(byte[] b)
{
return octetsNeeded(CODES, b);
}
public static void encode(ByteBuffer buffer, String s)
{
encode(CODES, buffer, s);
}
public static void encode(ByteBuffer buffer, byte[] b)
{
encode(CODES, buffer, b);
}
public static int octetsNeededLC(String s)
{
return octetsNeeded(LCCODES, s);
}
public static void encodeLC(ByteBuffer buffer, String s)
{
encode(LCCODES, buffer, s);
}
private static int octetsNeeded(final int[][] table, String s)
{
int needed = 0;
int len = s.length();
for (int i = 0; i < len; i++)
{
char c = s.charAt(i);
if (c >= 128 || c < ' ')
return -1;
needed += table[c][1];
}
return (needed + 7) / 8;
}
private static int octetsNeeded(final int[][] table, byte[] b)
{
int needed = 0;
int len = b.length;
for (int i = 0; i < len; i++)
{
int c = 0xFF & b[i];
needed += table[c][1];
}
return (needed + 7) / 8;
}
/**
* @param table The table to encode by
* @param buffer The buffer to encode to
* @param s The string to encode
*/
private static void encode(final int[][] table, ByteBuffer buffer, String s)
{
long current = 0;
int n = 0;
int len = s.length();
for (int i = 0; i < len; i++)
{
char c = s.charAt(i);
if (c >= 128 || c < ' ')
throw new IllegalArgumentException();
int code = table[c][0];
int bits = table[c][1];
current <<= bits;
current |= code;
n += bits;
while (n >= 8)
{
n -= 8;
buffer.put((byte)(current >> n));
}
}
if (n > 0)
{
current <<= (8 - n);
current |= (0xFF >>> n);
buffer.put((byte)(current));
}
}
private static void encode(final int[][] table, ByteBuffer buffer, byte[] b)
{
long current = 0;
int n = 0;
int len = b.length;
for (int i = 0; i < len; i++)
{
int c = 0xFF & b[i];
int code = table[c][0];
int bits = table[c][1];
current <<= bits;
current |= code;
n += bits;
while (n >= 8)
{
n -= 8;
buffer.put((byte)(current >> n));
}
}
if (n > 0)
{
current <<= (8 - n);
current |= (0xFF >>> n);
buffer.put((byte)(current));
}
}
} }

View File

@ -11,18 +11,28 @@
// ======================================================================== // ========================================================================
// //
package org.eclipse.jetty.http3.qpack.internal.util; package org.eclipse.jetty.http.compression;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import org.eclipse.jetty.util.Utf8StringBuilder; import org.eclipse.jetty.util.Utf8StringBuilder;
import static org.eclipse.jetty.http.compression.Huffman.rowbits;
import static org.eclipse.jetty.http.compression.Huffman.rowsym;
public class HuffmanDecoder public class HuffmanDecoder
{ {
static final char EOS = HuffmanEncoder.EOS; public static String decode(ByteBuffer buffer, int length) throws EncodingException
static final char[] tree = HuffmanEncoder.tree; {
static final char[] rowsym = HuffmanEncoder.rowsym; HuffmanDecoder huffmanDecoder = new HuffmanDecoder();
static final byte[] rowbits = HuffmanEncoder.rowbits; huffmanDecoder.setLength(length);
String decoded = huffmanDecoder.decode(buffer);
if (decoded == null)
throw new EncodingException("invalid string encoding");
huffmanDecoder.reset();
return decoded;
}
private final Utf8StringBuilder _utf8 = new Utf8StringBuilder(); private final Utf8StringBuilder _utf8 = new Utf8StringBuilder();
private int _length = 0; private int _length = 0;
@ -51,10 +61,10 @@ public class HuffmanDecoder
while (_bits >= 8) while (_bits >= 8)
{ {
int c = (_current >>> (_bits - 8)) & 0xFF; int c = (_current >>> (_bits - 8)) & 0xFF;
_node = tree[_node * 256 + c]; _node = Huffman.tree[_node * 256 + c];
if (rowbits[_node] != 0) if (rowbits[_node] != 0)
{ {
if (rowsym[_node] == EOS) if (rowsym[_node] == Huffman.EOS)
{ {
reset(); reset();
throw new EncodingException("eos_in_content"); throw new EncodingException("eos_in_content");
@ -77,7 +87,7 @@ public class HuffmanDecoder
{ {
int c = (_current << (8 - _bits)) & 0xFF; int c = (_current << (8 - _bits)) & 0xFF;
int lastNode = _node; int lastNode = _node;
_node = tree[_node * 256 + c]; _node = Huffman.tree[_node * 256 + c];
if (rowbits[_node] == 0 || rowbits[_node] > _bits) if (rowbits[_node] == 0 || rowbits[_node] > _bits)
{ {

View File

@ -0,0 +1,144 @@
//
// ========================================================================
// 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.http.compression;
import java.nio.ByteBuffer;
import static org.eclipse.jetty.http.compression.Huffman.CODES;
import static org.eclipse.jetty.http.compression.Huffman.LCCODES;
public class HuffmanEncoder
{
private HuffmanEncoder()
{
}
public static int octetsNeeded(String s)
{
return octetsNeeded(CODES, s);
}
public static int octetsNeeded(byte[] b)
{
int needed = 0;
for (byte value : b)
{
int c = 0xFF & value;
needed += CODES[c][1];
}
return (needed + 7) / 8;
}
public static void encode(ByteBuffer buffer, String s)
{
encode(CODES, buffer, s);
}
public static void encode(ByteBuffer buffer, byte[] b)
{
encode(CODES, buffer, b);
}
public static int octetsNeededLC(String s)
{
return octetsNeeded(LCCODES, s);
}
public static void encodeLC(ByteBuffer buffer, String s)
{
encode(LCCODES, buffer, s);
}
private static int octetsNeeded(final int[][] table, String s)
{
int needed = 0;
int len = s.length();
for (int i = 0; i < len; i++)
{
char c = s.charAt(i);
if (c >= 128 || c < ' ')
return -1;
needed += table[c][1];
}
return (needed + 7) / 8;
}
/**
* @param table The table to encode by
* @param buffer The buffer to encode to
* @param s The string to encode
*/
private static void encode(final int[][] table, ByteBuffer buffer, String s)
{
long current = 0;
int n = 0;
int len = s.length();
for (int i = 0; i < len; i++)
{
char c = s.charAt(i);
if (c >= 128 || c < ' ')
throw new IllegalArgumentException();
int code = table[c][0];
int bits = table[c][1];
current <<= bits;
current |= code;
n += bits;
while (n >= 8)
{
n -= 8;
buffer.put((byte)(current >> n));
}
}
if (n > 0)
{
current <<= (8 - n);
current |= (0xFF >>> n);
buffer.put((byte)(current));
}
}
private static void encode(final int[][] table, ByteBuffer buffer, byte[] b)
{
long current = 0;
int n = 0;
for (byte value : b)
{
int c = 0xFF & value;
int code = table[c][0];
int bits = table[c][1];
current <<= bits;
current |= code;
n += bits;
while (n >= 8)
{
n -= 8;
buffer.put((byte)(current >> n));
}
}
if (n > 0)
{
current <<= (8 - n);
current |= (0xFF >>> n);
buffer.put((byte)(current));
}
}
}

View File

@ -11,13 +11,13 @@
// ======================================================================== // ========================================================================
// //
package org.eclipse.jetty.http3.qpack.internal.util; package org.eclipse.jetty.http.compression;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
public class NBitIntegerEncoder public class NBitIntegerEncoder
{ {
public static int octectsNeeded(int n, long i) public static int octetsNeeded(int n, long i)
{ {
if (n == 8) if (n == 8)
{ {

View File

@ -11,12 +11,27 @@
// ======================================================================== // ========================================================================
// //
package org.eclipse.jetty.http3.qpack.internal.util; package org.eclipse.jetty.http.compression;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
public class NBitIntegerParser public class NBitIntegerParser
{ {
public static int decode(ByteBuffer buffer, int prefix) throws EncodingException
{
// TODO: This is a fix for HPACK as it already takes the first byte of the encoded integer.
if (prefix != 8)
buffer.position(buffer.position() - 1);
NBitIntegerParser parser = new NBitIntegerParser();
parser.setPrefix(prefix);
int decodedInt = parser.decodeInt(buffer);
if (decodedInt < 0)
throw new EncodingException("invalid integer encoding");
parser.reset();
return decodedInt;
}
private int _prefix; private int _prefix;
private long _total; private long _total;
private long _multiplier; private long _multiplier;

View File

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

View File

@ -16,9 +16,6 @@ package org.eclipse.jetty.http2.hpack;
import org.eclipse.jetty.http.HostPortHttpField; import org.eclipse.jetty.http.HostPortHttpField;
import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeader;
/**
*
*/
public class AuthorityHttpField extends HostPortHttpField public class AuthorityHttpField extends HostPortHttpField
{ {
public static final String AUTHORITY = HpackContext.STATIC_TABLE[1][0]; public static final String AUTHORITY = HpackContext.STATIC_TABLE[1][0];

View File

@ -24,6 +24,8 @@ import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.compression.HuffmanEncoder;
import org.eclipse.jetty.http.compression.NBitIntegerEncoder;
import org.eclipse.jetty.util.Index; import org.eclipse.jetty.util.Index;
import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.StringUtil;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -457,19 +459,19 @@ public class HpackContext
String value = field.getValue(); String value = field.getValue();
if (value != null && value.length() > 0) if (value != null && value.length() > 0)
{ {
int huffmanLen = Huffman.octetsNeeded(value); int huffmanLen = HuffmanEncoder.octetsNeeded(value);
if (huffmanLen < 0) if (huffmanLen < 0)
throw new IllegalStateException("bad value"); throw new IllegalStateException("bad value");
int lenLen = NBitInteger.octectsNeeded(7, huffmanLen); int lenLen = NBitIntegerEncoder.octetsNeeded(7, huffmanLen);
_huffmanValue = new byte[1 + lenLen + huffmanLen]; _huffmanValue = new byte[1 + lenLen + huffmanLen];
ByteBuffer buffer = ByteBuffer.wrap(_huffmanValue); ByteBuffer buffer = ByteBuffer.wrap(_huffmanValue);
// Indicate Huffman // Indicate Huffman
buffer.put((byte)0x80); buffer.put((byte)0x80);
// Add huffman length // Add huffman length
NBitInteger.encode(buffer, 7, huffmanLen); NBitIntegerEncoder.encode(buffer, 7, huffmanLen);
// Encode value // Encode value
Huffman.encode(buffer, value); HuffmanEncoder.encode(buffer, value);
} }
else else
_huffmanValue = null; _huffmanValue = null;

View File

@ -19,6 +19,9 @@ import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpTokens; import org.eclipse.jetty.http.HttpTokens;
import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http.compression.EncodingException;
import org.eclipse.jetty.http.compression.HuffmanDecoder;
import org.eclipse.jetty.http.compression.NBitIntegerParser;
import org.eclipse.jetty.http2.hpack.HpackContext.Entry; import org.eclipse.jetty.http2.hpack.HpackContext.Entry;
import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.BufferUtil;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -79,7 +82,7 @@ public class HpackDecoder
if (b < 0) if (b < 0)
{ {
// 7.1 indexed if the high bit is set // 7.1 indexed if the high bit is set
int index = NBitInteger.decode(buffer, 7); int index = integerDecode(buffer, 7);
Entry entry = _context.get(index); Entry entry = _context.get(index);
if (entry == null) if (entry == null)
throw new HpackException.SessionException("Unknown index %d", index); throw new HpackException.SessionException("Unknown index %d", index);
@ -120,7 +123,7 @@ public class HpackDecoder
case 2: // 7.3 case 2: // 7.3
case 3: // 7.3 case 3: // 7.3
// change table size // change table size
int size = NBitInteger.decode(buffer, 5); int size = integerDecode(buffer, 5);
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("decode resize={}", size); LOG.debug("decode resize={}", size);
if (size > _localMaxDynamicTableSize) if (size > _localMaxDynamicTableSize)
@ -133,7 +136,7 @@ public class HpackDecoder
case 0: // 7.2.2 case 0: // 7.2.2
case 1: // 7.2.3 case 1: // 7.2.3
indexed = false; indexed = false;
nameIndex = NBitInteger.decode(buffer, 4); nameIndex = integerDecode(buffer, 4);
break; break;
case 4: // 7.2.1 case 4: // 7.2.1
@ -141,7 +144,7 @@ public class HpackDecoder
case 6: // 7.2.1 case 6: // 7.2.1
case 7: // 7.2.1 case 7: // 7.2.1
indexed = true; indexed = true;
nameIndex = NBitInteger.decode(buffer, 6); nameIndex = integerDecode(buffer, 6);
break; break;
default: default:
@ -160,10 +163,10 @@ public class HpackDecoder
else else
{ {
huffmanName = (buffer.get() & 0x80) == 0x80; huffmanName = (buffer.get() & 0x80) == 0x80;
int length = NBitInteger.decode(buffer, 7); int length = integerDecode(buffer, 7);
_builder.checkSize(length, huffmanName); _builder.checkSize(length, huffmanName);
if (huffmanName) if (huffmanName)
name = Huffman.decode(buffer, length); name = huffmanDecode(buffer, length);
else else
name = toASCIIString(buffer, length); name = toASCIIString(buffer, length);
check: check:
@ -201,10 +204,10 @@ public class HpackDecoder
// decode the value // decode the value
boolean huffmanValue = (buffer.get() & 0x80) == 0x80; boolean huffmanValue = (buffer.get() & 0x80) == 0x80;
int length = NBitInteger.decode(buffer, 7); int length = integerDecode(buffer, 7);
_builder.checkSize(length, huffmanValue); _builder.checkSize(length, huffmanValue);
if (huffmanValue) if (huffmanValue)
value = Huffman.decode(buffer, length); value = huffmanDecode(buffer, length);
else else
value = toASCIIString(buffer, length); value = toASCIIString(buffer, length);
@ -267,6 +270,34 @@ public class HpackDecoder
return _builder.build(); return _builder.build();
} }
private int integerDecode(ByteBuffer buffer, int prefix) throws HpackException.CompressionException
{
try
{
return NBitIntegerParser.decode(buffer, prefix);
}
catch (EncodingException e)
{
HpackException.CompressionException compressionException = new HpackException.CompressionException(e.getMessage());
compressionException.initCause(e);
throw compressionException;
}
}
private String huffmanDecode(ByteBuffer buffer, int length) throws HpackException.CompressionException
{
try
{
return HuffmanDecoder.decode(buffer, length);
}
catch (EncodingException e)
{
HpackException.CompressionException compressionException = new HpackException.CompressionException(e.getMessage());
compressionException.initCause(e);
throw compressionException;
}
}
public static String toASCIIString(ByteBuffer buffer, int length) public static String toASCIIString(ByteBuffer buffer, int length)
{ {
StringBuilder builder = new StringBuilder(length); StringBuilder builder = new StringBuilder(length);

View File

@ -29,6 +29,8 @@ import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http.PreEncodedHttpField; import org.eclipse.jetty.http.PreEncodedHttpField;
import org.eclipse.jetty.http.compression.HuffmanEncoder;
import org.eclipse.jetty.http.compression.NBitIntegerEncoder;
import org.eclipse.jetty.http2.hpack.HpackContext.Entry; import org.eclipse.jetty.http2.hpack.HpackContext.Entry;
import org.eclipse.jetty.http2.hpack.HpackContext.StaticEntry; import org.eclipse.jetty.http2.hpack.HpackContext.StaticEntry;
import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.BufferUtil;
@ -286,7 +288,7 @@ public class HpackEncoder
if (maxDynamicTableSize > _remoteMaxDynamicTableSize) if (maxDynamicTableSize > _remoteMaxDynamicTableSize)
throw new IllegalArgumentException(); throw new IllegalArgumentException();
buffer.put((byte)0x20); buffer.put((byte)0x20);
NBitInteger.encode(buffer, 5, maxDynamicTableSize); NBitIntegerEncoder.encode(buffer, 5, maxDynamicTableSize);
_context.resize(maxDynamicTableSize); _context.resize(maxDynamicTableSize);
} }
@ -315,9 +317,9 @@ public class HpackEncoder
{ {
int index = _context.index(entry); int index = _context.index(entry);
buffer.put((byte)0x80); buffer.put((byte)0x80);
NBitInteger.encode(buffer, 7, index); NBitIntegerEncoder.encode(buffer, 7, index);
if (_debug) if (_debug)
encoding = "IdxField" + (entry.isStatic() ? "S" : "") + (1 + NBitInteger.octectsNeeded(7, index)); encoding = "IdxField" + (entry.isStatic() ? "S" : "") + (1 + NBitIntegerEncoder.octetsNeeded(7, index));
} }
} }
else else
@ -391,19 +393,19 @@ public class HpackEncoder
if (_debug) if (_debug)
encoding = "Lit" + encoding = "Lit" +
((name == null) ? "HuffN" : ("IdxN" + (name.isStatic() ? "S" : "") + (1 + NBitInteger.octectsNeeded(4, _context.index(name))))) + ((name == null) ? "HuffN" : ("IdxN" + (name.isStatic() ? "S" : "") + (1 + NBitIntegerEncoder.octetsNeeded(4, _context.index(name))))) +
(huffman ? "HuffV" : "LitV") + (huffman ? "HuffV" : "LitV") +
(neverIndex ? "!!Idx" : "!Idx"); (neverIndex ? "!!Idx" : "!Idx");
} }
else if (fieldSize >= _context.getMaxDynamicTableSize() || header == HttpHeader.CONTENT_LENGTH && !"0".equals(field.getValue())) else if (fieldSize >= _context.getMaxDynamicTableSize() || header == HttpHeader.CONTENT_LENGTH && !"0".equals(field.getValue()))
{ {
// The field is too large or a non zero content length, so do not index. // The field is too large or a non-zero content length, so do not index.
indexed = false; indexed = false;
encodeName(buffer, (byte)0x00, 4, header.asString(), name); encodeName(buffer, (byte)0x00, 4, header.asString(), name);
encodeValue(buffer, true, field.getValue()); encodeValue(buffer, true, field.getValue());
if (_debug) if (_debug)
encoding = "Lit" + encoding = "Lit" +
((name == null) ? "HuffN" : "IdxNS" + (1 + NBitInteger.octectsNeeded(4, _context.index(name)))) + ((name == null) ? "HuffN" : "IdxNS" + (1 + NBitIntegerEncoder.octetsNeeded(4, _context.index(name)))) +
"HuffV!Idx"; "HuffV!Idx";
} }
else else
@ -414,7 +416,7 @@ public class HpackEncoder
encodeName(buffer, (byte)0x40, 6, header.asString(), name); encodeName(buffer, (byte)0x40, 6, header.asString(), name);
encodeValue(buffer, huffman, field.getValue()); encodeValue(buffer, huffman, field.getValue());
if (_debug) if (_debug)
encoding = ((name == null) ? "LitHuffN" : ("LitIdxN" + (name.isStatic() ? "S" : "") + (1 + NBitInteger.octectsNeeded(6, _context.index(name))))) + encoding = ((name == null) ? "LitHuffN" : ("LitIdxN" + (name.isStatic() ? "S" : "") + (1 + NBitIntegerEncoder.octetsNeeded(6, _context.index(name))))) +
(huffman ? "HuffVIdx" : "LitVIdx"); (huffman ? "HuffVIdx" : "LitVIdx");
} }
} }
@ -439,12 +441,12 @@ public class HpackEncoder
// leave name index bits as 0 // leave name index bits as 0
// Encode the name always with lowercase huffman // Encode the name always with lowercase huffman
buffer.put((byte)0x80); buffer.put((byte)0x80);
NBitInteger.encode(buffer, 7, Huffman.octetsNeededLC(name)); NBitIntegerEncoder.encode(buffer, 7, HuffmanEncoder.octetsNeededLC(name));
Huffman.encodeLC(buffer, name); HuffmanEncoder.encodeLC(buffer, name);
} }
else else
{ {
NBitInteger.encode(buffer, bits, _context.index(entry)); NBitIntegerEncoder.encode(buffer, bits, _context.index(entry));
} }
} }
@ -455,25 +457,25 @@ public class HpackEncoder
// huffman literal value // huffman literal value
buffer.put((byte)0x80); buffer.put((byte)0x80);
int needed = Huffman.octetsNeeded(value); int needed = HuffmanEncoder.octetsNeeded(value);
if (needed >= 0) if (needed >= 0)
{ {
NBitInteger.encode(buffer, 7, needed); NBitIntegerEncoder.encode(buffer, 7, needed);
Huffman.encode(buffer, value); HuffmanEncoder.encode(buffer, value);
} }
else else
{ {
// Not iso_8859_1 // Not iso_8859_1
byte[] bytes = value.getBytes(StandardCharsets.UTF_8); byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
NBitInteger.encode(buffer, 7, Huffman.octetsNeeded(bytes)); NBitIntegerEncoder.encode(buffer, 7, HuffmanEncoder.octetsNeeded(bytes));
Huffman.encode(buffer, bytes); HuffmanEncoder.encode(buffer, bytes);
} }
} }
else else
{ {
// add literal assuming iso_8859_1 // add literal assuming iso_8859_1
buffer.put((byte)0x00).mark(); buffer.put((byte)0x00).mark();
NBitInteger.encode(buffer, 7, value.length()); NBitIntegerEncoder.encode(buffer, 7, value.length());
for (int i = 0; i < value.length(); i++) for (int i = 0; i < value.length(); i++)
{ {
char c = value.charAt(i); char c = value.charAt(i);
@ -482,7 +484,7 @@ public class HpackEncoder
// Not iso_8859_1, so re-encode as UTF-8 // Not iso_8859_1, so re-encode as UTF-8
buffer.reset(); buffer.reset();
byte[] bytes = value.getBytes(StandardCharsets.UTF_8); byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
NBitInteger.encode(buffer, 7, bytes.length); NBitIntegerEncoder.encode(buffer, 7, bytes.length);
buffer.put(bytes, 0, bytes.length); buffer.put(bytes, 0, bytes.length);
return; return;
} }

View File

@ -13,7 +13,6 @@
package org.eclipse.jetty.http2.hpack; package org.eclipse.jetty.http2.hpack;
@SuppressWarnings("serial")
public abstract class HpackException extends Exception public abstract class HpackException extends Exception
{ {
HpackException(String messageFormat, Object... args) HpackException(String messageFormat, Object... args)
@ -30,7 +29,7 @@ public abstract class HpackException extends Exception
*/ */
public static class StreamException extends HpackException public static class StreamException extends HpackException
{ {
StreamException(String messageFormat, Object... args) public StreamException(String messageFormat, Object... args)
{ {
super(messageFormat, args); super(messageFormat, args);
} }
@ -43,7 +42,7 @@ public abstract class HpackException extends Exception
*/ */
public static class SessionException extends HpackException public static class SessionException extends HpackException
{ {
SessionException(String messageFormat, Object... args) public SessionException(String messageFormat, Object... args)
{ {
super(messageFormat, args); super(messageFormat, args);
} }

View File

@ -18,6 +18,8 @@ import java.nio.ByteBuffer;
import org.eclipse.jetty.http.HttpFieldPreEncoder; import org.eclipse.jetty.http.HttpFieldPreEncoder;
import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.compression.HuffmanEncoder;
import org.eclipse.jetty.http.compression.NBitIntegerEncoder;
import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.BufferUtil;
/** /**
@ -67,12 +69,12 @@ public class HpackFieldPreEncoder implements HttpFieldPreEncoder
int nameIdx = HpackContext.staticIndex(header); int nameIdx = HpackContext.staticIndex(header);
if (nameIdx > 0) if (nameIdx > 0)
NBitInteger.encode(buffer, bits, nameIdx); NBitIntegerEncoder.encode(buffer, bits, nameIdx);
else else
{ {
buffer.put((byte)0x80); buffer.put((byte)0x80);
NBitInteger.encode(buffer, 7, Huffman.octetsNeededLC(name)); NBitIntegerEncoder.encode(buffer, 7, HuffmanEncoder.octetsNeededLC(name));
Huffman.encodeLC(buffer, name); HuffmanEncoder.encodeLC(buffer, name);
} }
HpackEncoder.encodeValue(buffer, huffman, value); HpackEncoder.encodeValue(buffer, huffman, value);

View File

@ -34,7 +34,7 @@ public class MetaDataBuilder
private HostPortHttpField _authority; private HostPortHttpField _authority;
private String _path; private String _path;
private String _protocol; private String _protocol;
private long _contentLength = Long.MIN_VALUE; private long _contentLength = -1;
private HpackException.StreamException _streamException; private HpackException.StreamException _streamException;
private boolean _request; private boolean _request;
private boolean _response; private boolean _response;
@ -67,17 +67,17 @@ public class MetaDataBuilder
return _size; return _size;
} }
public void emit(HttpField field) throws HpackException.SessionException public void emit(HttpField field) throws SessionException
{ {
HttpHeader header = field.getHeader(); HttpHeader header = field.getHeader();
String name = field.getName(); String name = field.getName();
if (name == null || name.length() == 0) if (name == null || name.length() == 0)
throw new HpackException.SessionException("Header size 0"); throw new SessionException("Header size 0");
String value = field.getValue(); String value = field.getValue();
int fieldSize = name.length() + (value == null ? 0 : value.length()); int fieldSize = name.length() + (value == null ? 0 : value.length());
_size += fieldSize + 32; _size += fieldSize + 32;
if (_size > _maxSize) if (_size > _maxSize)
throw new HpackException.SessionException("Header size %d > %d", _size, _maxSize); throw new SessionException("Header size %d > %d", _size, _maxSize);
if (field instanceof StaticTableHttpField) if (field instanceof StaticTableHttpField)
{ {
@ -196,7 +196,7 @@ public class MetaDataBuilder
} }
} }
protected void streamException(String messageFormat, Object... args) public void streamException(String messageFormat, Object... args)
{ {
HpackException.StreamException stream = new HpackException.StreamException(messageFormat, args); HpackException.StreamException stream = new HpackException.StreamException(messageFormat, args);
if (_streamException == null) if (_streamException == null)
@ -277,7 +277,7 @@ public class MetaDataBuilder
_path = null; _path = null;
_protocol = null; _protocol = null;
_size = 0; _size = 0;
_contentLength = Long.MIN_VALUE; _contentLength = -1;
} }
} }
@ -294,6 +294,6 @@ public class MetaDataBuilder
if (huffman) if (huffman)
length = (length * 4) / 3; length = (length * 4) / 3;
if ((_size + length) > _maxSize) if ((_size + length) > _maxSize)
throw new HpackException.SessionException("Header too large %d > %d", _size + length, _maxSize); throw new SessionException("Header too large %d > %d", _size + length, _maxSize);
} }
} }

View File

@ -1,146 +0,0 @@
//
// ========================================================================
// 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.http2.hpack;
import java.nio.ByteBuffer;
public class NBitInteger
{
public static int octectsNeeded(int n, int i)
{
if (n == 8)
{
int nbits = 0xFF;
i = i - nbits;
if (i < 0)
return 1;
if (i == 0)
return 2;
int lz = Integer.numberOfLeadingZeros(i);
int log = 32 - lz;
return 1 + (log + 6) / 7;
}
int nbits = 0xFF >>> (8 - n);
i = i - nbits;
if (i < 0)
return 0;
if (i == 0)
return 1;
int lz = Integer.numberOfLeadingZeros(i);
int log = 32 - lz;
return (log + 6) / 7;
}
public static void encode(ByteBuffer buf, int n, int i)
{
if (n == 8)
{
if (i < 0xFF)
{
buf.put((byte)i);
}
else
{
buf.put((byte)0xFF);
int length = i - 0xFF;
while (true)
{
if ((length & ~0x7F) == 0)
{
buf.put((byte)length);
return;
}
else
{
buf.put((byte)((length & 0x7F) | 0x80));
length >>>= 7;
}
}
}
}
else
{
int p = buf.position() - 1;
int bits = 0xFF >>> (8 - n);
if (i < bits)
{
buf.put(p, (byte)((buf.get(p) & ~bits) | i));
}
else
{
buf.put(p, (byte)(buf.get(p) | bits));
int length = i - bits;
while (true)
{
if ((length & ~0x7F) == 0)
{
buf.put((byte)length);
return;
}
else
{
buf.put((byte)((length & 0x7F) | 0x80));
length >>>= 7;
}
}
}
}
}
public static int decode(ByteBuffer buffer, int n)
{
if (n == 8)
{
int nbits = 0xFF;
int i = buffer.get() & 0xff;
if (i == nbits)
{
int m = 1;
int b;
do
{
b = 0xff & buffer.get();
i = i + (b & 127) * m;
m = m * 128;
}
while ((b & 128) == 128);
}
return i;
}
int nbits = 0xFF >>> (8 - n);
int i = buffer.get(buffer.position() - 1) & nbits;
if (i == nbits)
{
int m = 1;
int b;
do
{
b = 0xff & buffer.get();
i = i + (b & 127) * m;
m = m * 128;
}
while ((b & 128) == 128);
}
return i;
}
}

View File

@ -16,6 +16,8 @@ package org.eclipse.jetty.http2.hpack;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.compression.HuffmanDecoder;
import org.eclipse.jetty.http.compression.NBitIntegerParser;
import org.eclipse.jetty.http2.hpack.HpackContext.Entry; import org.eclipse.jetty.http2.hpack.HpackContext.Entry;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -423,10 +425,10 @@ public class HpackContextTest
int huff = 0xff & buffer.get(); int huff = 0xff & buffer.get();
assertTrue((0x80 & huff) == 0x80); assertTrue((0x80 & huff) == 0x80);
int len = NBitInteger.decode(buffer, 7); int len = NBitIntegerParser.decode(buffer, 7);
assertEquals(len, buffer.remaining()); assertEquals(len, buffer.remaining());
String value = Huffman.decode(buffer); String value = HuffmanDecoder.decode(buffer, buffer.remaining());
assertEquals(entry.getHttpField().getValue(), value); assertEquals(entry.getHttpField().getValue(), value);
} }

View File

@ -18,8 +18,10 @@ import java.nio.ByteBuffer;
import java.util.Locale; import java.util.Locale;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.eclipse.jetty.http.compression.HuffmanDecoder;
import org.eclipse.jetty.http.compression.HuffmanEncoder;
import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.util.StringUtil;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.Arguments;
@ -50,8 +52,8 @@ public class HuffmanTest
@MethodSource("data") @MethodSource("data")
public void testDecode(String specSection, String hex, String expected) throws Exception public void testDecode(String specSection, String hex, String expected) throws Exception
{ {
byte[] encoded = TypeUtil.fromHexString(hex); byte[] encoded = StringUtil.fromHexString(hex);
String decoded = Huffman.decode(ByteBuffer.wrap(encoded)); String decoded = HuffmanDecoder.decode(ByteBuffer.wrap(encoded), encoded.length);
assertEquals(expected, decoded, specSection); assertEquals(expected, decoded, specSection);
} }
@ -61,11 +63,11 @@ public class HuffmanTest
{ {
ByteBuffer buf = BufferUtil.allocate(1024); ByteBuffer buf = BufferUtil.allocate(1024);
int pos = BufferUtil.flipToFill(buf); int pos = BufferUtil.flipToFill(buf);
Huffman.encode(buf, expected); HuffmanEncoder.encode(buf, expected);
BufferUtil.flipToFlush(buf, pos); BufferUtil.flipToFlush(buf, pos);
String encoded = TypeUtil.toHexString(BufferUtil.toArray(buf)).toLowerCase(Locale.ENGLISH); String encoded = StringUtil.toHexString(BufferUtil.toArray(buf)).toLowerCase(Locale.ENGLISH);
assertEquals(hex, encoded, specSection); assertEquals(hex, encoded, specSection);
assertEquals(hex.length() / 2, Huffman.octetsNeeded(expected)); assertEquals(hex.length() / 2, HuffmanEncoder.octetsNeeded(expected));
} }
@ParameterizedTest(name = "[{index}]") // don't include unprintable character in test display-name @ParameterizedTest(name = "[{index}]") // don't include unprintable character in test display-name
@ -74,9 +76,9 @@ public class HuffmanTest
{ {
String s = "bad '" + bad + "'"; String s = "bad '" + bad + "'";
assertThat(Huffman.octetsNeeded(s), Matchers.is(-1)); assertThat(HuffmanEncoder.octetsNeeded(s), Matchers.is(-1));
assertThrows(BufferOverflowException.class, assertThrows(BufferOverflowException.class,
() -> Huffman.encode(BufferUtil.allocate(32), s)); () -> HuffmanEncoder.encode(BufferUtil.allocate(32), s));
} }
} }

View File

@ -15,8 +15,10 @@ package org.eclipse.jetty.http2.hpack;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import org.eclipse.jetty.http.compression.NBitIntegerEncoder;
import org.eclipse.jetty.http.compression.NBitIntegerParser;
import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.util.StringUtil;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
@ -27,17 +29,17 @@ public class NBitIntegerTest
@Test @Test
public void testOctetsNeeded() public void testOctetsNeeded()
{ {
assertEquals(0, NBitInteger.octectsNeeded(5, 10)); assertEquals(0, NBitIntegerEncoder.octetsNeeded(5, 10));
assertEquals(2, NBitInteger.octectsNeeded(5, 1337)); assertEquals(2, NBitIntegerEncoder.octetsNeeded(5, 1337));
assertEquals(1, NBitInteger.octectsNeeded(8, 42)); assertEquals(1, NBitIntegerEncoder.octetsNeeded(8, 42));
assertEquals(3, NBitInteger.octectsNeeded(8, 1337)); assertEquals(3, NBitIntegerEncoder.octetsNeeded(8, 1337));
assertEquals(0, NBitInteger.octectsNeeded(6, 62)); assertEquals(0, NBitIntegerEncoder.octetsNeeded(6, 62));
assertEquals(1, NBitInteger.octectsNeeded(6, 63)); assertEquals(1, NBitIntegerEncoder.octetsNeeded(6, 63));
assertEquals(1, NBitInteger.octectsNeeded(6, 64)); assertEquals(1, NBitIntegerEncoder.octetsNeeded(6, 64));
assertEquals(2, NBitInteger.octectsNeeded(6, 63 + 0x00 + 0x80 * 0x01)); assertEquals(2, NBitIntegerEncoder.octetsNeeded(6, 63 + 0x00 + 0x80 * 0x01));
assertEquals(3, NBitInteger.octectsNeeded(6, 63 + 0x00 + 0x80 * 0x80)); assertEquals(3, NBitIntegerEncoder.octetsNeeded(6, 63 + 0x00 + 0x80 * 0x80));
assertEquals(4, NBitInteger.octectsNeeded(6, 63 + 0x00 + 0x80 * 0x80 * 0x80)); assertEquals(4, NBitIntegerEncoder.octetsNeeded(6, 63 + 0x00 + 0x80 * 0x80 * 0x80));
} }
@Test @Test
@ -78,16 +80,16 @@ public class NBitIntegerTest
int p = BufferUtil.flipToFill(buf); int p = BufferUtil.flipToFill(buf);
if (n < 8) if (n < 8)
buf.put((byte)0x00); buf.put((byte)0x00);
NBitInteger.encode(buf, n, i); NBitIntegerEncoder.encode(buf, n, i);
BufferUtil.flipToFlush(buf, p); BufferUtil.flipToFlush(buf, p);
String r = TypeUtil.toHexString(BufferUtil.toArray(buf)); String r = StringUtil.toHexString(BufferUtil.toArray(buf));
assertEquals(expected, r); assertEquals(expected, r);
assertEquals(expected.length() / 2, (n < 8 ? 1 : 0) + NBitInteger.octectsNeeded(n, i)); assertEquals(expected.length() / 2, (n < 8 ? 1 : 0) + NBitIntegerEncoder.octetsNeeded(n, i));
} }
@Test @Test
public void testDecode() public void testDecode() throws Exception
{ {
testDecode(6, 0, "00"); testDecode(6, 0, "00");
testDecode(6, 1, "01"); testDecode(6, 1, "01");
@ -118,11 +120,11 @@ public class NBitIntegerTest
testDecode(8, 255 + 0x00 + 0x80 * 0x80, "Ff808001"); testDecode(8, 255 + 0x00 + 0x80 * 0x80, "Ff808001");
} }
public void testDecode(int n, int expected, String encoded) public void testDecode(int n, int expected, String encoded) throws Exception
{ {
ByteBuffer buf = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); ByteBuffer buf = ByteBuffer.wrap(StringUtil.fromHexString(encoded));
buf.position(n == 8 ? 0 : 1); buf.position(n == 8 ? 0 : 1);
assertEquals(expected, NBitInteger.decode(buf, n)); assertEquals(expected, NBitIntegerParser.decode(buf, n));
} }
@Test @Test
@ -132,21 +134,21 @@ public class NBitIntegerTest
int p = BufferUtil.flipToFill(buf); int p = BufferUtil.flipToFill(buf);
buf.put((byte)0x77); buf.put((byte)0x77);
buf.put((byte)0xFF); buf.put((byte)0xFF);
NBitInteger.encode(buf, 5, 10); NBitIntegerEncoder.encode(buf, 5, 10);
BufferUtil.flipToFlush(buf, p); BufferUtil.flipToFlush(buf, p);
String r = TypeUtil.toHexString(BufferUtil.toArray(buf)); String r = StringUtil.toHexString(BufferUtil.toArray(buf));
assertEquals("77Ea", r); assertEquals("77Ea", r);
} }
@Test @Test
public void testDecodeExampleD11() public void testDecodeExampleD11() throws Exception
{ {
ByteBuffer buf = ByteBuffer.wrap(TypeUtil.fromHexString("77EaFF")); ByteBuffer buf = ByteBuffer.wrap(StringUtil.fromHexString("77EaFF"));
buf.position(2); buf.position(2);
assertEquals(10, NBitInteger.decode(buf, 5)); assertEquals(10, NBitIntegerParser.decode(buf, 5));
} }
@Test @Test
@ -156,21 +158,21 @@ public class NBitIntegerTest
int p = BufferUtil.flipToFill(buf); int p = BufferUtil.flipToFill(buf);
buf.put((byte)0x88); buf.put((byte)0x88);
buf.put((byte)0x00); buf.put((byte)0x00);
NBitInteger.encode(buf, 5, 1337); NBitIntegerEncoder.encode(buf, 5, 1337);
BufferUtil.flipToFlush(buf, p); BufferUtil.flipToFlush(buf, p);
String r = TypeUtil.toHexString(BufferUtil.toArray(buf)); String r = StringUtil.toHexString(BufferUtil.toArray(buf));
assertEquals("881f9a0a", r); assertEquals("881f9a0a", r);
} }
@Test @Test
public void testDecodeExampleD12() public void testDecodeExampleD12() throws Exception
{ {
ByteBuffer buf = ByteBuffer.wrap(TypeUtil.fromHexString("881f9a0aff")); ByteBuffer buf = ByteBuffer.wrap(StringUtil.fromHexString("881f9a0aff"));
buf.position(2); buf.position(2);
assertEquals(1337, NBitInteger.decode(buf, 5)); assertEquals(1337, NBitIntegerParser.decode(buf, 5));
} }
@Test @Test
@ -180,20 +182,20 @@ public class NBitIntegerTest
int p = BufferUtil.flipToFill(buf); int p = BufferUtil.flipToFill(buf);
buf.put((byte)0x88); buf.put((byte)0x88);
buf.put((byte)0xFF); buf.put((byte)0xFF);
NBitInteger.encode(buf, 8, 42); NBitIntegerEncoder.encode(buf, 8, 42);
BufferUtil.flipToFlush(buf, p); BufferUtil.flipToFlush(buf, p);
String r = TypeUtil.toHexString(BufferUtil.toArray(buf)); String r = StringUtil.toHexString(BufferUtil.toArray(buf));
assertEquals("88Ff2a", r); assertEquals("88Ff2a", r);
} }
@Test @Test
public void testDecodeExampleD13() public void testDecodeExampleD13() throws Exception
{ {
ByteBuffer buf = ByteBuffer.wrap(TypeUtil.fromHexString("882aFf")); ByteBuffer buf = ByteBuffer.wrap(StringUtil.fromHexString("882aFf"));
buf.position(1); buf.position(1);
assertEquals(42, NBitInteger.decode(buf, 8)); assertEquals(42, NBitIntegerParser.decode(buf, 8));
} }
} }

View File

@ -24,6 +24,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http.compression.NBitIntegerParser;
import org.eclipse.jetty.http3.qpack.internal.QpackContext; import org.eclipse.jetty.http3.qpack.internal.QpackContext;
import org.eclipse.jetty.http3.qpack.internal.instruction.InsertCountIncrementInstruction; import org.eclipse.jetty.http3.qpack.internal.instruction.InsertCountIncrementInstruction;
import org.eclipse.jetty.http3.qpack.internal.instruction.SectionAcknowledgmentInstruction; import org.eclipse.jetty.http3.qpack.internal.instruction.SectionAcknowledgmentInstruction;
@ -33,7 +34,6 @@ import org.eclipse.jetty.http3.qpack.internal.parser.EncodedFieldSection;
import org.eclipse.jetty.http3.qpack.internal.table.DynamicTable; 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.table.Entry;
import org.eclipse.jetty.http3.qpack.internal.table.StaticTable; import org.eclipse.jetty.http3.qpack.internal.table.StaticTable;
import org.eclipse.jetty.http3.qpack.internal.util.NBitIntegerParser;
import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.component.Dumpable; import org.eclipse.jetty.util.component.Dumpable;
import org.slf4j.Logger; import org.slf4j.Logger;

View File

@ -26,6 +26,7 @@ import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http.PreEncodedHttpField; import org.eclipse.jetty.http.PreEncodedHttpField;
import org.eclipse.jetty.http.compression.NBitIntegerEncoder;
import org.eclipse.jetty.http3.qpack.internal.EncodableEntry; import org.eclipse.jetty.http3.qpack.internal.EncodableEntry;
import org.eclipse.jetty.http3.qpack.internal.QpackContext; import org.eclipse.jetty.http3.qpack.internal.QpackContext;
import org.eclipse.jetty.http3.qpack.internal.StreamInfo; import org.eclipse.jetty.http3.qpack.internal.StreamInfo;
@ -37,7 +38,6 @@ 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.parser.EncoderInstructionParser;
import org.eclipse.jetty.http3.qpack.internal.table.DynamicTable; 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.table.Entry;
import org.eclipse.jetty.http3.qpack.internal.util.NBitIntegerEncoder;
import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.component.Dumpable; import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.thread.AutoLock; import org.eclipse.jetty.util.thread.AutoLock;

View File

@ -19,9 +19,9 @@ import java.util.Objects;
import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.PreEncodedHttpField; import org.eclipse.jetty.http.PreEncodedHttpField;
import org.eclipse.jetty.http.compression.HuffmanEncoder;
import org.eclipse.jetty.http.compression.NBitIntegerEncoder;
import org.eclipse.jetty.http3.qpack.internal.table.Entry; 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.NBitIntegerEncoder;
public abstract class EncodableEntry public abstract class EncodableEntry
{ {
@ -95,19 +95,19 @@ public abstract class EncodableEntry
{ {
// Indexed Field Line with Static Reference. // Indexed Field Line with Static Reference.
int relativeIndex = _entry.getIndex(); int relativeIndex = _entry.getIndex();
return 1 + NBitIntegerEncoder.octectsNeeded(6, relativeIndex); return 1 + NBitIntegerEncoder.octetsNeeded(6, relativeIndex);
} }
else if (_entry.getIndex() < base) else if (_entry.getIndex() < base)
{ {
// Indexed Field Line with Dynamic Reference. // Indexed Field Line with Dynamic Reference.
int relativeIndex = base - (_entry.getIndex() + 1); int relativeIndex = base - (_entry.getIndex() + 1);
return 1 + NBitIntegerEncoder.octectsNeeded(6, relativeIndex); return 1 + NBitIntegerEncoder.octetsNeeded(6, relativeIndex);
} }
else else
{ {
// Indexed Field Line with Post-Base Index. // Indexed Field Line with Post-Base Index.
int relativeIndex = _entry.getIndex() - base; int relativeIndex = _entry.getIndex() - base;
return 1 + NBitIntegerEncoder.octectsNeeded(4, relativeIndex); return 1 + NBitIntegerEncoder.octetsNeeded(4, relativeIndex);
} }
} }
@ -183,7 +183,7 @@ public abstract class EncodableEntry
String value = getValue(); String value = getValue();
int relativeIndex = _nameEntry.getIndex() - base; int relativeIndex = _nameEntry.getIndex() - base;
int valueLength = _huffman ? HuffmanEncoder.octetsNeeded(value) : value.length(); int valueLength = _huffman ? HuffmanEncoder.octetsNeeded(value) : value.length();
return 1 + NBitIntegerEncoder.octectsNeeded(4, relativeIndex) + 1 + NBitIntegerEncoder.octectsNeeded(7, valueLength) + valueLength; return 1 + NBitIntegerEncoder.octetsNeeded(4, relativeIndex) + 1 + NBitIntegerEncoder.octetsNeeded(7, valueLength) + valueLength;
} }
@Override @Override
@ -246,7 +246,7 @@ public abstract class EncodableEntry
String value = getValue(); String value = getValue();
int nameLength = _huffman ? HuffmanEncoder.octetsNeeded(name) : name.length(); int nameLength = _huffman ? HuffmanEncoder.octetsNeeded(name) : name.length();
int valueLength = _huffman ? HuffmanEncoder.octetsNeeded(value) : value.length(); int valueLength = _huffman ? HuffmanEncoder.octetsNeeded(value) : value.length();
return 2 + NBitIntegerEncoder.octectsNeeded(3, nameLength) + nameLength + NBitIntegerEncoder.octectsNeeded(7, valueLength) + valueLength; return 2 + NBitIntegerEncoder.octetsNeeded(3, nameLength) + nameLength + NBitIntegerEncoder.octetsNeeded(7, valueLength) + valueLength;
} }
@Override @Override

View File

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

View File

@ -15,9 +15,9 @@ package org.eclipse.jetty.http3.qpack.internal.instruction;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import org.eclipse.jetty.http.compression.HuffmanEncoder;
import org.eclipse.jetty.http.compression.NBitIntegerEncoder;
import org.eclipse.jetty.http3.qpack.Instruction; import org.eclipse.jetty.http3.qpack.Instruction;
import org.eclipse.jetty.http3.qpack.internal.util.HuffmanEncoder;
import org.eclipse.jetty.http3.qpack.internal.util.NBitIntegerEncoder;
import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.BufferUtil;
@ -54,7 +54,7 @@ public class IndexedNameEntryInstruction implements Instruction
@Override @Override
public void encode(ByteBufferPool.Lease lease) public void encode(ByteBufferPool.Lease lease)
{ {
int size = NBitIntegerEncoder.octectsNeeded(6, _index) + (_huffman ? HuffmanEncoder.octetsNeeded(_value) : _value.length()) + 2; int size = NBitIntegerEncoder.octetsNeeded(6, _index) + (_huffman ? HuffmanEncoder.octetsNeeded(_value) : _value.length()) + 2;
ByteBuffer buffer = lease.acquire(size, false); ByteBuffer buffer = lease.acquire(size, false);
// First bit indicates the instruction, second bit is whether it is a dynamic table reference or not. // First bit indicates the instruction, second bit is whether it is a dynamic table reference or not.

View File

@ -15,8 +15,8 @@ package org.eclipse.jetty.http3.qpack.internal.instruction;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import org.eclipse.jetty.http.compression.NBitIntegerEncoder;
import org.eclipse.jetty.http3.qpack.Instruction; import org.eclipse.jetty.http3.qpack.Instruction;
import org.eclipse.jetty.http3.qpack.internal.util.NBitIntegerEncoder;
import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.BufferUtil;
@ -37,7 +37,7 @@ public class InsertCountIncrementInstruction implements Instruction
@Override @Override
public void encode(ByteBufferPool.Lease lease) public void encode(ByteBufferPool.Lease lease)
{ {
int size = NBitIntegerEncoder.octectsNeeded(6, _increment) + 1; int size = NBitIntegerEncoder.octetsNeeded(6, _increment) + 1;
ByteBuffer buffer = lease.acquire(size, false); ByteBuffer buffer = lease.acquire(size, false);
buffer.put((byte)0x00); buffer.put((byte)0x00);
NBitIntegerEncoder.encode(buffer, 6, _increment); NBitIntegerEncoder.encode(buffer, 6, _increment);

View File

@ -16,9 +16,9 @@ package org.eclipse.jetty.http3.qpack.internal.instruction;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.compression.HuffmanEncoder;
import org.eclipse.jetty.http.compression.NBitIntegerEncoder;
import org.eclipse.jetty.http3.qpack.Instruction; import org.eclipse.jetty.http3.qpack.Instruction;
import org.eclipse.jetty.http3.qpack.internal.util.HuffmanEncoder;
import org.eclipse.jetty.http3.qpack.internal.util.NBitIntegerEncoder;
import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.BufferUtil;

View File

@ -15,8 +15,8 @@ package org.eclipse.jetty.http3.qpack.internal.instruction;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import org.eclipse.jetty.http.compression.NBitIntegerEncoder;
import org.eclipse.jetty.http3.qpack.Instruction; import org.eclipse.jetty.http3.qpack.Instruction;
import org.eclipse.jetty.http3.qpack.internal.util.NBitIntegerEncoder;
import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.BufferUtil;
@ -37,7 +37,7 @@ public class SectionAcknowledgmentInstruction implements Instruction
@Override @Override
public void encode(ByteBufferPool.Lease lease) public void encode(ByteBufferPool.Lease lease)
{ {
int size = NBitIntegerEncoder.octectsNeeded(7, _streamId) + 1; int size = NBitIntegerEncoder.octetsNeeded(7, _streamId) + 1;
ByteBuffer buffer = lease.acquire(size, false); ByteBuffer buffer = lease.acquire(size, false);
buffer.put((byte)0x80); buffer.put((byte)0x80);
NBitIntegerEncoder.encode(buffer, 7, _streamId); NBitIntegerEncoder.encode(buffer, 7, _streamId);

View File

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

View File

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

View File

@ -15,10 +15,10 @@ package org.eclipse.jetty.http3.qpack.internal.parser;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import org.eclipse.jetty.http.compression.EncodingException;
import org.eclipse.jetty.http.compression.NBitIntegerParser;
import org.eclipse.jetty.http.compression.NBitStringParser;
import org.eclipse.jetty.http3.qpack.QpackException; import org.eclipse.jetty.http3.qpack.QpackException;
import org.eclipse.jetty.http3.qpack.internal.util.EncodingException;
import org.eclipse.jetty.http3.qpack.internal.util.NBitIntegerParser;
import org.eclipse.jetty.http3.qpack.internal.util.NBitStringParser;
/** /**
* Parses a stream of unframed instructions for the Decoder. These instructions are sent from the remote Encoder. * Parses a stream of unframed instructions for the Decoder. These instructions are sent from the remote Encoder.

View File

@ -19,13 +19,13 @@ import java.util.List;
import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http.compression.EncodingException;
import org.eclipse.jetty.http.compression.NBitIntegerParser;
import org.eclipse.jetty.http.compression.NBitStringParser;
import org.eclipse.jetty.http3.qpack.QpackDecoder; import org.eclipse.jetty.http3.qpack.QpackDecoder;
import org.eclipse.jetty.http3.qpack.QpackException; import org.eclipse.jetty.http3.qpack.QpackException;
import org.eclipse.jetty.http3.qpack.internal.QpackContext; import org.eclipse.jetty.http3.qpack.internal.QpackContext;
import org.eclipse.jetty.http3.qpack.internal.metadata.MetaDataBuilder; import org.eclipse.jetty.http3.qpack.internal.metadata.MetaDataBuilder;
import org.eclipse.jetty.http3.qpack.internal.util.EncodingException;
import org.eclipse.jetty.http3.qpack.internal.util.NBitIntegerParser;
import org.eclipse.jetty.http3.qpack.internal.util.NBitStringParser;
import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.BufferUtil;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;

View File

@ -15,8 +15,8 @@ package org.eclipse.jetty.http3.qpack.internal.parser;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import org.eclipse.jetty.http.compression.NBitIntegerParser;
import org.eclipse.jetty.http3.qpack.QpackException; import org.eclipse.jetty.http3.qpack.QpackException;
import org.eclipse.jetty.http3.qpack.internal.util.NBitIntegerParser;
/** /**
* Parses a stream of unframed instructions for the Encoder. These instructions are sent from the remote Decoder. * Parses a stream of unframed instructions for the Encoder. These instructions are sent from the remote Decoder.

View File

@ -17,8 +17,8 @@ import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http3.qpack.internal.util.HuffmanEncoder; import org.eclipse.jetty.http.compression.HuffmanEncoder;
import org.eclipse.jetty.http3.qpack.internal.util.NBitIntegerEncoder; import org.eclipse.jetty.http.compression.NBitIntegerEncoder;
import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.StringUtil;
public class Entry public class Entry
@ -119,7 +119,7 @@ public class Entry
int huffmanLen = HuffmanEncoder.octetsNeeded(value); int huffmanLen = HuffmanEncoder.octetsNeeded(value);
if (huffmanLen < 0) if (huffmanLen < 0)
throw new IllegalStateException("bad value"); throw new IllegalStateException("bad value");
int lenLen = NBitIntegerEncoder.octectsNeeded(7, huffmanLen); int lenLen = NBitIntegerEncoder.octetsNeeded(7, huffmanLen);
_huffmanValue = new byte[1 + lenLen + huffmanLen]; _huffmanValue = new byte[1 + lenLen + huffmanLen];
ByteBuffer buffer = ByteBuffer.wrap(_huffmanValue); ByteBuffer buffer = ByteBuffer.wrap(_huffmanValue);

View File

@ -1,473 +0,0 @@
//
// ========================================================================
// 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.internal.util;
import java.nio.ByteBuffer;
public class HuffmanEncoder
{
// Appendix C: Huffman Codes
// http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#appendix-C
static final int[][] CODES =
{
/* ( 0) |11111111|11000 */ {0x1ff8, 13},
/* ( 1) |11111111|11111111|1011000 */ {0x7fffd8, 23},
/* ( 2) |11111111|11111111|11111110|0010 */ {0xfffffe2, 28},
/* ( 3) |11111111|11111111|11111110|0011 */ {0xfffffe3, 28},
/* ( 4) |11111111|11111111|11111110|0100 */ {0xfffffe4, 28},
/* ( 5) |11111111|11111111|11111110|0101 */ {0xfffffe5, 28},
/* ( 6) |11111111|11111111|11111110|0110 */ {0xfffffe6, 28},
/* ( 7) |11111111|11111111|11111110|0111 */ {0xfffffe7, 28},
/* ( 8) |11111111|11111111|11111110|1000 */ {0xfffffe8, 28},
/* ( 9) |11111111|11111111|11101010 */ {0xffffea, 24},
/* ( 10) |11111111|11111111|11111111|111100 */ {0x3ffffffc, 30},
/* ( 11) |11111111|11111111|11111110|1001 */ {0xfffffe9, 28},
/* ( 12) |11111111|11111111|11111110|1010 */ {0xfffffea, 28},
/* ( 13) |11111111|11111111|11111111|111101 */ {0x3ffffffd, 30},
/* ( 14) |11111111|11111111|11111110|1011 */ {0xfffffeb, 28},
/* ( 15) |11111111|11111111|11111110|1100 */ {0xfffffec, 28},
/* ( 16) |11111111|11111111|11111110|1101 */ {0xfffffed, 28},
/* ( 17) |11111111|11111111|11111110|1110 */ {0xfffffee, 28},
/* ( 18) |11111111|11111111|11111110|1111 */ {0xfffffef, 28},
/* ( 19) |11111111|11111111|11111111|0000 */ {0xffffff0, 28},
/* ( 20) |11111111|11111111|11111111|0001 */ {0xffffff1, 28},
/* ( 21) |11111111|11111111|11111111|0010 */ {0xffffff2, 28},
/* ( 22) |11111111|11111111|11111111|111110 */ {0x3ffffffe, 30},
/* ( 23) |11111111|11111111|11111111|0011 */ {0xffffff3, 28},
/* ( 24) |11111111|11111111|11111111|0100 */ {0xffffff4, 28},
/* ( 25) |11111111|11111111|11111111|0101 */ {0xffffff5, 28},
/* ( 26) |11111111|11111111|11111111|0110 */ {0xffffff6, 28},
/* ( 27) |11111111|11111111|11111111|0111 */ {0xffffff7, 28},
/* ( 28) |11111111|11111111|11111111|1000 */ {0xffffff8, 28},
/* ( 29) |11111111|11111111|11111111|1001 */ {0xffffff9, 28},
/* ( 30) |11111111|11111111|11111111|1010 */ {0xffffffa, 28},
/* ( 31) |11111111|11111111|11111111|1011 */ {0xffffffb, 28},
/*' ' ( 32) |010100 */ {0x14, 6},
/*'!' ( 33) |11111110|00 */ {0x3f8, 10},
/*'"' ( 34) |11111110|01 */ {0x3f9, 10},
/*'#' ( 35) |11111111|1010 */ {0xffa, 12},
/*'$' ( 36) |11111111|11001 */ {0x1ff9, 13},
/*'%' ( 37) |010101 */ {0x15, 6},
/*'&' ( 38) |11111000 */ {0xf8, 8},
/*''' ( 39) |11111111|010 */ {0x7fa, 11},
/*'(' ( 40) |11111110|10 */ {0x3fa, 10},
/*')' ( 41) |11111110|11 */ {0x3fb, 10},
/*'*' ( 42) |11111001 */ {0xf9, 8},
/*'+' ( 43) |11111111|011 */ {0x7fb, 11},
/*',' ( 44) |11111010 */ {0xfa, 8},
/*'-' ( 45) |010110 */ {0x16, 6},
/*'.' ( 46) |010111 */ {0x17, 6},
/*'/' ( 47) |011000 */ {0x18, 6},
/*'0' ( 48) |00000 */ {0x0, 5},
/*'1' ( 49) |00001 */ {0x1, 5},
/*'2' ( 50) |00010 */ {0x2, 5},
/*'3' ( 51) |011001 */ {0x19, 6},
/*'4' ( 52) |011010 */ {0x1a, 6},
/*'5' ( 53) |011011 */ {0x1b, 6},
/*'6' ( 54) |011100 */ {0x1c, 6},
/*'7' ( 55) |011101 */ {0x1d, 6},
/*'8' ( 56) |011110 */ {0x1e, 6},
/*'9' ( 57) |011111 */ {0x1f, 6},
/*':' ( 58) |1011100 */ {0x5c, 7},
/*';' ( 59) |11111011 */ {0xfb, 8},
/*'<' ( 60) |11111111|1111100 */ {0x7ffc, 15},
/*'=' ( 61) |100000 */ {0x20, 6},
/*'>' ( 62) |11111111|1011 */ {0xffb, 12},
/*'?' ( 63) |11111111|00 */ {0x3fc, 10},
/*'@' ( 64) |11111111|11010 */ {0x1ffa, 13},
/*'A' ( 65) |100001 */ {0x21, 6},
/*'B' ( 66) |1011101 */ {0x5d, 7},
/*'C' ( 67) |1011110 */ {0x5e, 7},
/*'D' ( 68) |1011111 */ {0x5f, 7},
/*'E' ( 69) |1100000 */ {0x60, 7},
/*'F' ( 70) |1100001 */ {0x61, 7},
/*'G' ( 71) |1100010 */ {0x62, 7},
/*'H' ( 72) |1100011 */ {0x63, 7},
/*'I' ( 73) |1100100 */ {0x64, 7},
/*'J' ( 74) |1100101 */ {0x65, 7},
/*'K' ( 75) |1100110 */ {0x66, 7},
/*'L' ( 76) |1100111 */ {0x67, 7},
/*'M' ( 77) |1101000 */ {0x68, 7},
/*'N' ( 78) |1101001 */ {0x69, 7},
/*'O' ( 79) |1101010 */ {0x6a, 7},
/*'P' ( 80) |1101011 */ {0x6b, 7},
/*'Q' ( 81) |1101100 */ {0x6c, 7},
/*'R' ( 82) |1101101 */ {0x6d, 7},
/*'S' ( 83) |1101110 */ {0x6e, 7},
/*'T' ( 84) |1101111 */ {0x6f, 7},
/*'U' ( 85) |1110000 */ {0x70, 7},
/*'V' ( 86) |1110001 */ {0x71, 7},
/*'W' ( 87) |1110010 */ {0x72, 7},
/*'X' ( 88) |11111100 */ {0xfc, 8},
/*'Y' ( 89) |1110011 */ {0x73, 7},
/*'Z' ( 90) |11111101 */ {0xfd, 8},
/*'[' ( 91) |11111111|11011 */ {0x1ffb, 13},
/*'\' ( 92) |11111111|11111110|000 */ {0x7fff0, 19},
/*']' ( 93) |11111111|11100 */ {0x1ffc, 13},
/*'^' ( 94) |11111111|111100 */ {0x3ffc, 14},
/*'_' ( 95) |100010 */ {0x22, 6},
/*'`' ( 96) |11111111|1111101 */ {0x7ffd, 15},
/*'a' ( 97) |00011 */ {0x3, 5},
/*'b' ( 98) |100011 */ {0x23, 6},
/*'c' ( 99) |00100 */ {0x4, 5},
/*'d' (100) |100100 */ {0x24, 6},
/*'e' (101) |00101 */ {0x5, 5},
/*'f' (102) |100101 */ {0x25, 6},
/*'g' (103) |100110 */ {0x26, 6},
/*'h' (104) |100111 */ {0x27, 6},
/*'i' (105) |00110 */ {0x6, 5},
/*'j' (106) |1110100 */ {0x74, 7},
/*'k' (107) |1110101 */ {0x75, 7},
/*'l' (108) |101000 */ {0x28, 6},
/*'m' (109) |101001 */ {0x29, 6},
/*'n' (110) |101010 */ {0x2a, 6},
/*'o' (111) |00111 */ {0x7, 5},
/*'p' (112) |101011 */ {0x2b, 6},
/*'q' (113) |1110110 */ {0x76, 7},
/*'r' (114) |101100 */ {0x2c, 6},
/*'s' (115) |01000 */ {0x8, 5},
/*'t' (116) |01001 */ {0x9, 5},
/*'u' (117) |101101 */ {0x2d, 6},
/*'v' (118) |1110111 */ {0x77, 7},
/*'w' (119) |1111000 */ {0x78, 7},
/*'x' (120) |1111001 */ {0x79, 7},
/*'y' (121) |1111010 */ {0x7a, 7},
/*'z' (122) |1111011 */ {0x7b, 7},
/*'{' (123) |11111111|1111110 */ {0x7ffe, 15},
/*'|' (124) |11111111|100 */ {0x7fc, 11},
/*'}' (125) |11111111|111101 */ {0x3ffd, 14},
/*'~' (126) |11111111|11101 */ {0x1ffd, 13},
/* (127) |11111111|11111111|11111111|1100 */ {0xffffffc, 28},
/* (128) |11111111|11111110|0110 */ {0xfffe6, 20},
/* (129) |11111111|11111111|010010 */ {0x3fffd2, 22},
/* (130) |11111111|11111110|0111 */ {0xfffe7, 20},
/* (131) |11111111|11111110|1000 */ {0xfffe8, 20},
/* (132) |11111111|11111111|010011 */ {0x3fffd3, 22},
/* (133) |11111111|11111111|010100 */ {0x3fffd4, 22},
/* (134) |11111111|11111111|010101 */ {0x3fffd5, 22},
/* (135) |11111111|11111111|1011001 */ {0x7fffd9, 23},
/* (136) |11111111|11111111|010110 */ {0x3fffd6, 22},
/* (137) |11111111|11111111|1011010 */ {0x7fffda, 23},
/* (138) |11111111|11111111|1011011 */ {0x7fffdb, 23},
/* (139) |11111111|11111111|1011100 */ {0x7fffdc, 23},
/* (140) |11111111|11111111|1011101 */ {0x7fffdd, 23},
/* (141) |11111111|11111111|1011110 */ {0x7fffde, 23},
/* (142) |11111111|11111111|11101011 */ {0xffffeb, 24},
/* (143) |11111111|11111111|1011111 */ {0x7fffdf, 23},
/* (144) |11111111|11111111|11101100 */ {0xffffec, 24},
/* (145) |11111111|11111111|11101101 */ {0xffffed, 24},
/* (146) |11111111|11111111|010111 */ {0x3fffd7, 22},
/* (147) |11111111|11111111|1100000 */ {0x7fffe0, 23},
/* (148) |11111111|11111111|11101110 */ {0xffffee, 24},
/* (149) |11111111|11111111|1100001 */ {0x7fffe1, 23},
/* (150) |11111111|11111111|1100010 */ {0x7fffe2, 23},
/* (151) |11111111|11111111|1100011 */ {0x7fffe3, 23},
/* (152) |11111111|11111111|1100100 */ {0x7fffe4, 23},
/* (153) |11111111|11111110|11100 */ {0x1fffdc, 21},
/* (154) |11111111|11111111|011000 */ {0x3fffd8, 22},
/* (155) |11111111|11111111|1100101 */ {0x7fffe5, 23},
/* (156) |11111111|11111111|011001 */ {0x3fffd9, 22},
/* (157) |11111111|11111111|1100110 */ {0x7fffe6, 23},
/* (158) |11111111|11111111|1100111 */ {0x7fffe7, 23},
/* (159) |11111111|11111111|11101111 */ {0xffffef, 24},
/* (160) |11111111|11111111|011010 */ {0x3fffda, 22},
/* (161) |11111111|11111110|11101 */ {0x1fffdd, 21},
/* (162) |11111111|11111110|1001 */ {0xfffe9, 20},
/* (163) |11111111|11111111|011011 */ {0x3fffdb, 22},
/* (164) |11111111|11111111|011100 */ {0x3fffdc, 22},
/* (165) |11111111|11111111|1101000 */ {0x7fffe8, 23},
/* (166) |11111111|11111111|1101001 */ {0x7fffe9, 23},
/* (167) |11111111|11111110|11110 */ {0x1fffde, 21},
/* (168) |11111111|11111111|1101010 */ {0x7fffea, 23},
/* (169) |11111111|11111111|011101 */ {0x3fffdd, 22},
/* (170) |11111111|11111111|011110 */ {0x3fffde, 22},
/* (171) |11111111|11111111|11110000 */ {0xfffff0, 24},
/* (172) |11111111|11111110|11111 */ {0x1fffdf, 21},
/* (173) |11111111|11111111|011111 */ {0x3fffdf, 22},
/* (174) |11111111|11111111|1101011 */ {0x7fffeb, 23},
/* (175) |11111111|11111111|1101100 */ {0x7fffec, 23},
/* (176) |11111111|11111111|00000 */ {0x1fffe0, 21},
/* (177) |11111111|11111111|00001 */ {0x1fffe1, 21},
/* (178) |11111111|11111111|100000 */ {0x3fffe0, 22},
/* (179) |11111111|11111111|00010 */ {0x1fffe2, 21},
/* (180) |11111111|11111111|1101101 */ {0x7fffed, 23},
/* (181) |11111111|11111111|100001 */ {0x3fffe1, 22},
/* (182) |11111111|11111111|1101110 */ {0x7fffee, 23},
/* (183) |11111111|11111111|1101111 */ {0x7fffef, 23},
/* (184) |11111111|11111110|1010 */ {0xfffea, 20},
/* (185) |11111111|11111111|100010 */ {0x3fffe2, 22},
/* (186) |11111111|11111111|100011 */ {0x3fffe3, 22},
/* (187) |11111111|11111111|100100 */ {0x3fffe4, 22},
/* (188) |11111111|11111111|1110000 */ {0x7ffff0, 23},
/* (189) |11111111|11111111|100101 */ {0x3fffe5, 22},
/* (190) |11111111|11111111|100110 */ {0x3fffe6, 22},
/* (191) |11111111|11111111|1110001 */ {0x7ffff1, 23},
/* (192) |11111111|11111111|11111000|00 */ {0x3ffffe0, 26},
/* (193) |11111111|11111111|11111000|01 */ {0x3ffffe1, 26},
/* (194) |11111111|11111110|1011 */ {0xfffeb, 20},
/* (195) |11111111|11111110|001 */ {0x7fff1, 19},
/* (196) |11111111|11111111|100111 */ {0x3fffe7, 22},
/* (197) |11111111|11111111|1110010 */ {0x7ffff2, 23},
/* (198) |11111111|11111111|101000 */ {0x3fffe8, 22},
/* (199) |11111111|11111111|11110110|0 */ {0x1ffffec, 25},
/* (200) |11111111|11111111|11111000|10 */ {0x3ffffe2, 26},
/* (201) |11111111|11111111|11111000|11 */ {0x3ffffe3, 26},
/* (202) |11111111|11111111|11111001|00 */ {0x3ffffe4, 26},
/* (203) |11111111|11111111|11111011|110 */ {0x7ffffde, 27},
/* (204) |11111111|11111111|11111011|111 */ {0x7ffffdf, 27},
/* (205) |11111111|11111111|11111001|01 */ {0x3ffffe5, 26},
/* (206) |11111111|11111111|11110001 */ {0xfffff1, 24},
/* (207) |11111111|11111111|11110110|1 */ {0x1ffffed, 25},
/* (208) |11111111|11111110|010 */ {0x7fff2, 19},
/* (209) |11111111|11111111|00011 */ {0x1fffe3, 21},
/* (210) |11111111|11111111|11111001|10 */ {0x3ffffe6, 26},
/* (211) |11111111|11111111|11111100|000 */ {0x7ffffe0, 27},
/* (212) |11111111|11111111|11111100|001 */ {0x7ffffe1, 27},
/* (213) |11111111|11111111|11111001|11 */ {0x3ffffe7, 26},
/* (214) |11111111|11111111|11111100|010 */ {0x7ffffe2, 27},
/* (215) |11111111|11111111|11110010 */ {0xfffff2, 24},
/* (216) |11111111|11111111|00100 */ {0x1fffe4, 21},
/* (217) |11111111|11111111|00101 */ {0x1fffe5, 21},
/* (218) |11111111|11111111|11111010|00 */ {0x3ffffe8, 26},
/* (219) |11111111|11111111|11111010|01 */ {0x3ffffe9, 26},
/* (220) |11111111|11111111|11111111|1101 */ {0xffffffd, 28},
/* (221) |11111111|11111111|11111100|011 */ {0x7ffffe3, 27},
/* (222) |11111111|11111111|11111100|100 */ {0x7ffffe4, 27},
/* (223) |11111111|11111111|11111100|101 */ {0x7ffffe5, 27},
/* (224) |11111111|11111110|1100 */ {0xfffec, 20},
/* (225) |11111111|11111111|11110011 */ {0xfffff3, 24},
/* (226) |11111111|11111110|1101 */ {0xfffed, 20},
/* (227) |11111111|11111111|00110 */ {0x1fffe6, 21},
/* (228) |11111111|11111111|101001 */ {0x3fffe9, 22},
/* (229) |11111111|11111111|00111 */ {0x1fffe7, 21},
/* (230) |11111111|11111111|01000 */ {0x1fffe8, 21},
/* (231) |11111111|11111111|1110011 */ {0x7ffff3, 23},
/* (232) |11111111|11111111|101010 */ {0x3fffea, 22},
/* (233) |11111111|11111111|101011 */ {0x3fffeb, 22},
/* (234) |11111111|11111111|11110111|0 */ {0x1ffffee, 25},
/* (235) |11111111|11111111|11110111|1 */ {0x1ffffef, 25},
/* (236) |11111111|11111111|11110100 */ {0xfffff4, 24},
/* (237) |11111111|11111111|11110101 */ {0xfffff5, 24},
/* (238) |11111111|11111111|11111010|10 */ {0x3ffffea, 26},
/* (239) |11111111|11111111|1110100 */ {0x7ffff4, 23},
/* (240) |11111111|11111111|11111010|11 */ {0x3ffffeb, 26},
/* (241) |11111111|11111111|11111100|110 */ {0x7ffffe6, 27},
/* (242) |11111111|11111111|11111011|00 */ {0x3ffffec, 26},
/* (243) |11111111|11111111|11111011|01 */ {0x3ffffed, 26},
/* (244) |11111111|11111111|11111100|111 */ {0x7ffffe7, 27},
/* (245) |11111111|11111111|11111101|000 */ {0x7ffffe8, 27},
/* (246) |11111111|11111111|11111101|001 */ {0x7ffffe9, 27},
/* (247) |11111111|11111111|11111101|010 */ {0x7ffffea, 27},
/* (248) |11111111|11111111|11111101|011 */ {0x7ffffeb, 27},
/* (249) |11111111|11111111|11111111|1110 */ {0xffffffe, 28},
/* (250) |11111111|11111111|11111101|100 */ {0x7ffffec, 27},
/* (251) |11111111|11111111|11111101|101 */ {0x7ffffed, 27},
/* (252) |11111111|11111111|11111101|110 */ {0x7ffffee, 27},
/* (253) |11111111|11111111|11111101|111 */ {0x7ffffef, 27},
/* (254) |11111111|11111111|11111110|000 */ {0x7fffff0, 27},
/* (255) |11111111|11111111|11111011|10 */ {0x3ffffee, 26},
/*EOS (256) |11111111|11111111|11111111|111111 */ {0x3fffffff, 30}
};
static final int[][] LCCODES = new int[CODES.length][];
static final char EOS = 256;
// Huffman decode tree stored in a flattened char array for good
// locality of reference.
static final char[] tree;
static final char[] rowsym;
static final byte[] rowbits;
// Build the Huffman lookup tree and LC TABLE
static
{
System.arraycopy(CODES, 0, LCCODES, 0, CODES.length);
for (int i = 'A'; i <= 'Z'; i++)
{
LCCODES[i] = LCCODES['a' + i - 'A'];
}
int r = 0;
for (int i = 0; i < CODES.length; i++)
{
r += (CODES[i][1] + 7) / 8;
}
tree = new char[r * 256];
rowsym = new char[r];
rowbits = new byte[r];
r = 0;
for (int sym = 0; sym < CODES.length; sym++)
{
int code = CODES[sym][0];
int len = CODES[sym][1];
int current = 0;
while (len > 8)
{
len -= 8;
int i = ((code >>> len) & 0xFF);
int t = current * 256 + i;
current = tree[t];
if (current == 0)
{
tree[t] = (char)++r;
current = r;
}
}
int terminal = ++r;
rowsym[r] = (char)sym;
int b = len & 0x07;
int terminalBits = b == 0 ? 8 : b;
rowbits[r] = (byte)terminalBits;
int shift = 8 - len;
int start = current * 256 + ((code << shift) & 0xFF);
int end = start + (1 << shift);
for (int i = start; i < end; i++)
{
tree[i] = (char)terminal;
}
}
}
public static int octetsNeeded(String s)
{
return octetsNeeded(CODES, s);
}
public static int octetsNeeded(byte[] b)
{
return octetsNeeded(CODES, b);
}
public static void encode(ByteBuffer buffer, String s)
{
encode(CODES, buffer, s);
}
public static void encode(ByteBuffer buffer, byte[] b)
{
encode(CODES, buffer, b);
}
public static int octetsNeededLC(String s)
{
return octetsNeeded(LCCODES, s);
}
public static void encodeLC(ByteBuffer buffer, String s)
{
encode(LCCODES, buffer, s);
}
private static int octetsNeeded(final int[][] table, String s)
{
int needed = 0;
int len = s.length();
for (int i = 0; i < len; i++)
{
char c = s.charAt(i);
if (c >= 128 || c < ' ')
return -1;
needed += table[c][1];
}
return (needed + 7) / 8;
}
private static int octetsNeeded(final int[][] table, byte[] b)
{
int needed = 0;
int len = b.length;
for (int i = 0; i < len; i++)
{
int c = 0xFF & b[i];
needed += table[c][1];
}
return (needed + 7) / 8;
}
/**
* @param table The table to encode by
* @param buffer The buffer to encode to
* @param s The string to encode
*/
private static void encode(final int[][] table, ByteBuffer buffer, String s)
{
long current = 0;
int n = 0;
int len = s.length();
for (int i = 0; i < len; i++)
{
char c = s.charAt(i);
if (c >= 128 || c < ' ')
throw new IllegalArgumentException();
int code = table[c][0];
int bits = table[c][1];
current <<= bits;
current |= code;
n += bits;
while (n >= 8)
{
n -= 8;
buffer.put((byte)(current >> n));
}
}
if (n > 0)
{
current <<= (8 - n);
current |= (0xFF >>> n);
buffer.put((byte)(current));
}
}
private static void encode(final int[][] table, ByteBuffer buffer, byte[] b)
{
long current = 0;
int n = 0;
int len = b.length;
for (int i = 0; i < len; i++)
{
int c = 0xFF & b[i];
int code = table[c][0];
int bits = table[c][1];
current <<= bits;
current |= code;
n += bits;
while (n >= 8)
{
n -= 8;
buffer.put((byte)(current >> n));
}
}
if (n > 0)
{
current <<= (8 - n);
current |= (0xFF >>> n);
buffer.put((byte)(current));
}
}
}

View File

@ -18,8 +18,8 @@ import java.nio.ByteBuffer;
import java.util.Locale; import java.util.Locale;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.eclipse.jetty.http3.qpack.internal.util.HuffmanDecoder; import org.eclipse.jetty.http.compression.HuffmanDecoder;
import org.eclipse.jetty.http3.qpack.internal.util.HuffmanEncoder; import org.eclipse.jetty.http.compression.HuffmanEncoder;
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.hamcrest.Matchers; import org.hamcrest.Matchers;

View File

@ -15,7 +15,7 @@ package org.eclipse.jetty.http3.qpack;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import org.eclipse.jetty.http3.qpack.internal.util.NBitIntegerParser; import org.eclipse.jetty.http.compression.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;

View File

@ -15,8 +15,8 @@ package org.eclipse.jetty.http3.qpack;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import org.eclipse.jetty.http3.qpack.internal.util.NBitIntegerEncoder; import org.eclipse.jetty.http.compression.NBitIntegerEncoder;
import org.eclipse.jetty.http3.qpack.internal.util.NBitIntegerParser; import org.eclipse.jetty.http.compression.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;
@ -31,17 +31,17 @@ public class NBitIntegerTest
@Test @Test
public void testOctetsNeeded() public void testOctetsNeeded()
{ {
assertEquals(0, NBitIntegerEncoder.octectsNeeded(5, 10)); assertEquals(0, NBitIntegerEncoder.octetsNeeded(5, 10));
assertEquals(2, NBitIntegerEncoder.octectsNeeded(5, 1337)); assertEquals(2, NBitIntegerEncoder.octetsNeeded(5, 1337));
assertEquals(1, NBitIntegerEncoder.octectsNeeded(8, 42)); assertEquals(1, NBitIntegerEncoder.octetsNeeded(8, 42));
assertEquals(3, NBitIntegerEncoder.octectsNeeded(8, 1337)); assertEquals(3, NBitIntegerEncoder.octetsNeeded(8, 1337));
assertEquals(0, NBitIntegerEncoder.octectsNeeded(6, 62)); assertEquals(0, NBitIntegerEncoder.octetsNeeded(6, 62));
assertEquals(1, NBitIntegerEncoder.octectsNeeded(6, 63)); assertEquals(1, NBitIntegerEncoder.octetsNeeded(6, 63));
assertEquals(1, NBitIntegerEncoder.octectsNeeded(6, 64)); assertEquals(1, NBitIntegerEncoder.octetsNeeded(6, 64));
assertEquals(2, NBitIntegerEncoder.octectsNeeded(6, 63 + 0x00 + 0x80 * 0x01)); assertEquals(2, NBitIntegerEncoder.octetsNeeded(6, 63 + 0x00 + 0x80 * 0x01));
assertEquals(3, NBitIntegerEncoder.octectsNeeded(6, 63 + 0x00 + 0x80 * 0x80)); assertEquals(3, NBitIntegerEncoder.octetsNeeded(6, 63 + 0x00 + 0x80 * 0x80));
assertEquals(4, NBitIntegerEncoder.octectsNeeded(6, 63 + 0x00 + 0x80 * 0x80 * 0x80)); assertEquals(4, NBitIntegerEncoder.octetsNeeded(6, 63 + 0x00 + 0x80 * 0x80 * 0x80));
} }
@Test @Test
@ -87,7 +87,7 @@ public class NBitIntegerTest
String r = TypeUtil.toHexString(BufferUtil.toArray(buf)); String r = TypeUtil.toHexString(BufferUtil.toArray(buf));
assertEquals(expected, r); assertEquals(expected, r);
assertEquals(expected.length() / 2, (n < 8 ? 1 : 0) + NBitIntegerEncoder.octectsNeeded(n, i)); assertEquals(expected.length() / 2, (n < 8 ? 1 : 0) + NBitIntegerEncoder.octetsNeeded(n, i));
} }
@Test @Test