mirror of
https://github.com/openjdk/jdk.git
synced 2025-09-22 03:54:33 +02:00
8202423: Small HTTP Client refresh
Co-authored-by: Daniel Fuchs <daniel.fuchs@oracle.com> Co-authored-by: Michael McMahon <michael.x.mcmahon@oracle.com> Co-authored-by: Pavel Rappo <pavel.rappo@oracle.com> Reviewed-by: chegar, dfuchs, michaelm, prappo
This commit is contained in:
parent
d78303222b
commit
8e69e1b11f
53 changed files with 2770 additions and 1030 deletions
|
@ -61,6 +61,7 @@ class AsyncSSLConnection extends AbstractAsyncSSLConnection {
|
|||
// create the SSLTube wrapping the SocketTube, with the given engine
|
||||
flow = new SSLTube(engine,
|
||||
client().theExecutor(),
|
||||
client().getSSLBufferSupplier()::recycle,
|
||||
plainConnection.getConnectionFlow());
|
||||
return null; } );
|
||||
}
|
||||
|
|
|
@ -65,6 +65,7 @@ class AsyncSSLTunnelConnection extends AbstractAsyncSSLConnection {
|
|||
// create the SSLTube wrapping the SocketTube, with the given engine
|
||||
flow = new SSLTube(engine,
|
||||
client().theExecutor(),
|
||||
client().getSSLBufferSupplier()::recycle,
|
||||
plainConnection.getConnectionFlow());
|
||||
return null;} );
|
||||
}
|
||||
|
|
|
@ -356,6 +356,7 @@ class Http1AsyncReceiver {
|
|||
// be left over in the stream.
|
||||
try {
|
||||
setRetryOnError(false);
|
||||
pending.close(null);
|
||||
onReadError(new IOException("subscription cancelled"));
|
||||
unsubscribe(pending);
|
||||
} finally {
|
||||
|
|
|
@ -259,8 +259,19 @@ class Http1Exchange<T> extends ExchangeImpl<T> {
|
|||
if (!connection.connected()) {
|
||||
if (debug.on()) debug.log("initiating connect async");
|
||||
connectCF = connection.connectAsync();
|
||||
Throwable cancelled;
|
||||
synchronized (lock) {
|
||||
operations.add(connectCF);
|
||||
if ((cancelled = failed) == null) {
|
||||
operations.add(connectCF);
|
||||
}
|
||||
}
|
||||
if (cancelled != null) {
|
||||
if (client.isSelectorThread()) {
|
||||
executor.execute(() ->
|
||||
connectCF.completeExceptionally(cancelled));
|
||||
} else {
|
||||
connectCF.completeExceptionally(cancelled);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
connectCF = new MinimalFuture<>();
|
||||
|
@ -403,6 +414,9 @@ class Http1Exchange<T> extends ExchangeImpl<T> {
|
|||
if ((error = failed) == null) {
|
||||
failed = error = cause;
|
||||
}
|
||||
if (debug.on()) {
|
||||
debug.log(request.uri() + ": " + error);
|
||||
}
|
||||
if (requestAction != null && requestAction.finished()
|
||||
&& response != null && response.finished()) {
|
||||
return;
|
||||
|
@ -447,7 +461,7 @@ class Http1Exchange<T> extends ExchangeImpl<T> {
|
|||
exec.execute(() -> {
|
||||
if (cf.completeExceptionally(x)) {
|
||||
if (debug.on())
|
||||
debug.log("completed cf with %s", (Object) x);
|
||||
debug.log("%s: completed cf with %s", request.uri(), x);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -200,9 +200,9 @@ class Http1Request {
|
|||
query = "";
|
||||
}
|
||||
if (query.equals("")) {
|
||||
return path;
|
||||
return Utils.encode(path);
|
||||
} else {
|
||||
return path + "?" + query;
|
||||
return Utils.encode(path + "?" + query);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -375,12 +375,17 @@ class Http1Response<T> {
|
|||
(t) -> {
|
||||
try {
|
||||
if (t != null) {
|
||||
subscriber.onError(t);
|
||||
connection.close();
|
||||
cf.completeExceptionally(t);
|
||||
try {
|
||||
subscriber.onError(t);
|
||||
} finally {
|
||||
cf.completeExceptionally(t);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
bodyReader.onComplete(t);
|
||||
if (t != null) {
|
||||
connection.close();
|
||||
}
|
||||
}
|
||||
}));
|
||||
CompletableFuture<State> bodyReaderCF = bodyReader.completion();
|
||||
|
|
|
@ -95,10 +95,10 @@ class Http2ClientImpl {
|
|||
synchronized (this) {
|
||||
Http2Connection connection = connections.get(key);
|
||||
if (connection != null) {
|
||||
if (connection.closed) {
|
||||
if (connection.closed || !connection.reserveStream(true)) {
|
||||
if (debug.on())
|
||||
debug.log("removing found closed connection: %s", connection);
|
||||
connections.remove(key);
|
||||
debug.log("removing found closed or closing connection: %s", connection);
|
||||
deleteConnection(connection);
|
||||
} else {
|
||||
// fast path if connection already exists
|
||||
if (debug.on())
|
||||
|
@ -138,9 +138,9 @@ class Http2ClientImpl {
|
|||
*/
|
||||
boolean offerConnection(Http2Connection c) {
|
||||
if (debug.on()) debug.log("offering to the connection pool: %s", c);
|
||||
if (c.closed) {
|
||||
if (c.closed || c.finalStream()) {
|
||||
if (debug.on())
|
||||
debug.log("skipping offered closed connection: %s", c);
|
||||
debug.log("skipping offered closed or closing connection: %s", c);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -148,7 +148,7 @@ class Http2ClientImpl {
|
|||
synchronized(this) {
|
||||
Http2Connection c1 = connections.putIfAbsent(key, c);
|
||||
if (c1 != null) {
|
||||
c.setSingleStream(true);
|
||||
c.setFinalStream();
|
||||
if (debug.on())
|
||||
debug.log("existing entry in connection pool for %s", key);
|
||||
return false;
|
||||
|
@ -163,9 +163,12 @@ class Http2ClientImpl {
|
|||
if (debug.on())
|
||||
debug.log("removing from the connection pool: %s", c);
|
||||
synchronized (this) {
|
||||
connections.remove(c.key());
|
||||
if (debug.on())
|
||||
debug.log("removed from the connection pool: %s", c);
|
||||
Http2Connection c1 = connections.get(c.key());
|
||||
if (c1 != null && c1.equals(c)) {
|
||||
connections.remove(c.key());
|
||||
if (debug.on())
|
||||
debug.log("removed from the connection pool: %s", c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -121,33 +121,66 @@ class Http2Connection {
|
|||
Utils.getHpackLogger(this::dbgString, Utils.DEBUG_HPACK);
|
||||
static final ByteBuffer EMPTY_TRIGGER = ByteBuffer.allocate(0);
|
||||
|
||||
private boolean singleStream; // used only for stream 1, then closed
|
||||
static private final int MAX_CLIENT_STREAM_ID = Integer.MAX_VALUE; // 2147483647
|
||||
static private final int MAX_SERVER_STREAM_ID = Integer.MAX_VALUE - 1; // 2147483646
|
||||
|
||||
/**
|
||||
* Flag set when no more streams to be opened on this connection.
|
||||
* Two cases where it is used.
|
||||
*
|
||||
* 1. Two connections to the same server were opened concurrently, in which
|
||||
* case one of them will be put in the cache, and the second will expire
|
||||
* when all its opened streams (which usually should be a single client
|
||||
* stream + possibly some additional push-promise server streams) complete.
|
||||
* 2. A cached connection reaches its maximum number of streams (~ 2^31-1)
|
||||
* either server / or client allocated, in which case it will be taken
|
||||
* out of the cache - allowing a new connection to replace it. It will
|
||||
* expire when all its still open streams (which could be many) eventually
|
||||
* complete.
|
||||
*/
|
||||
private boolean finalStream;
|
||||
|
||||
/*
|
||||
* ByteBuffer pooling strategy for HTTP/2 protocol:
|
||||
* ByteBuffer pooling strategy for HTTP/2 protocol.
|
||||
*
|
||||
* In general there are 4 points where ByteBuffers are used:
|
||||
* - incoming/outgoing frames from/to ByteBuffers plus incoming/outgoing encrypted data
|
||||
* in case of SSL connection.
|
||||
* - incoming/outgoing frames from/to ByteBuffers plus incoming/outgoing
|
||||
* encrypted data in case of SSL connection.
|
||||
*
|
||||
* 1. Outgoing frames encoded to ByteBuffers.
|
||||
* Outgoing ByteBuffers are created with requited size and frequently small (except DataFrames, etc)
|
||||
* At this place no pools at all. All outgoing buffers should be collected by GC.
|
||||
*
|
||||
* Outgoing ByteBuffers are created with required size and frequently
|
||||
* small (except DataFrames, etc). At this place no pools at all. All
|
||||
* outgoing buffers should eventually be collected by GC.
|
||||
*
|
||||
* 2. Incoming ByteBuffers (decoded to frames).
|
||||
* Here, total elimination of BB pool is not a good idea.
|
||||
* We don't know how many bytes we will receive through network.
|
||||
* So here we allocate buffer of reasonable size. The following life of the BB:
|
||||
* - If all frames decoded from the BB are other than DataFrame and HeaderFrame (and HeaderFrame subclasses)
|
||||
* BB is returned to pool,
|
||||
* - If we decoded DataFrame from the BB. In that case DataFrame refers to subbuffer obtained by slice() method.
|
||||
* Such BB is never returned to pool and will be GCed.
|
||||
* - If we decoded HeadersFrame from the BB. Then header decoding is performed inside processFrame method and
|
||||
* the buffer could be release to pool.
|
||||
*
|
||||
* 3. SLL encrypted buffers. Here another pool was introduced and all net buffers are to/from the pool,
|
||||
* because of we can't predict size encrypted packets.
|
||||
* Here, total elimination of BB pool is not a good idea.
|
||||
* We don't know how many bytes we will receive through network.
|
||||
*
|
||||
* A possible future improvement ( currently not implemented ):
|
||||
* Allocate buffers of reasonable size. The following life of the BB:
|
||||
* - If all frames decoded from the BB are other than DataFrame and
|
||||
* HeaderFrame (and HeaderFrame subclasses) BB is returned to pool,
|
||||
* - If a DataFrame is decoded from the BB. In that case DataFrame refers
|
||||
* to sub-buffer obtained by slice(). Such a BB is never returned to the
|
||||
* pool and will eventually be GC'ed.
|
||||
* - If a HeadersFrame is decoded from the BB. Then header decoding is
|
||||
* performed inside processFrame method and the buffer could be release
|
||||
* back to pool.
|
||||
*
|
||||
* 3. SSL encrypted buffers ( received ).
|
||||
*
|
||||
* The current implementation recycles encrypted buffers read from the
|
||||
* channel. The pool of buffers has a maximum size of 3, SocketTube.MAX_BUFFERS,
|
||||
* direct buffers which are shared by all connections on a given client.
|
||||
* The pool is used by all SSL connections - whether HTTP/1.1 or HTTP/2,
|
||||
* but only for SSL encrypted buffers that circulate between the SocketTube
|
||||
* Publisher and the SSLFlowDelegate Reader. Limiting the pool to this
|
||||
* particular segment allows the use of direct buffers, thus avoiding any
|
||||
* additional copy in the NIO socket channel implementation. See
|
||||
* HttpClientImpl.SSLDirectBufferSupplier, SocketTube.SSLDirectBufferSource,
|
||||
* and SSLTube.recycler.
|
||||
*/
|
||||
|
||||
|
||||
|
@ -220,6 +253,12 @@ class Http2Connection {
|
|||
private final Map<Integer,Stream<?>> streams = new ConcurrentHashMap<>();
|
||||
private int nextstreamid;
|
||||
private int nextPushStream = 2;
|
||||
// actual stream ids are not allocated until the Headers frame is ready
|
||||
// to be sent. The following two fields are updated as soon as a stream
|
||||
// is created and assigned to a connection. They are checked before
|
||||
// assigning a stream to a connection.
|
||||
private int lastReservedClientStreamid = 1;
|
||||
private int lastReservedServerStreamid = 0;
|
||||
private final Encoder hpackOut;
|
||||
private final Decoder hpackIn;
|
||||
final SettingsFrame clientSettings;
|
||||
|
@ -365,6 +404,29 @@ class Http2Connection {
|
|||
return client2.client();
|
||||
}
|
||||
|
||||
// call these before assigning a request/stream to a connection
|
||||
// if false returned then a new Http2Connection is required
|
||||
// if true, the the stream may be assigned to this connection
|
||||
synchronized boolean reserveStream(boolean clientInitiated) {
|
||||
if (finalStream) {
|
||||
return false;
|
||||
}
|
||||
if (clientInitiated && (lastReservedClientStreamid + 2) >= MAX_CLIENT_STREAM_ID) {
|
||||
setFinalStream();
|
||||
client2.deleteConnection(this);
|
||||
return false;
|
||||
} else if (!clientInitiated && (lastReservedServerStreamid + 2) >= MAX_SERVER_STREAM_ID) {
|
||||
setFinalStream();
|
||||
client2.deleteConnection(this);
|
||||
return false;
|
||||
}
|
||||
if (clientInitiated)
|
||||
lastReservedClientStreamid+=2;
|
||||
else
|
||||
lastReservedServerStreamid+=2;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an IOException if h2 was not negotiated
|
||||
*/
|
||||
|
@ -414,12 +476,16 @@ class Http2Connection {
|
|||
.thenCompose(checkAlpnCF);
|
||||
}
|
||||
|
||||
synchronized boolean singleStream() {
|
||||
return singleStream;
|
||||
synchronized boolean finalStream() {
|
||||
return finalStream;
|
||||
}
|
||||
|
||||
synchronized void setSingleStream(boolean use) {
|
||||
singleStream = use;
|
||||
/**
|
||||
* Mark this connection so no more streams created on it and it will close when
|
||||
* all are complete.
|
||||
*/
|
||||
synchronized void setFinalStream() {
|
||||
finalStream = true;
|
||||
}
|
||||
|
||||
static String keyFor(HttpConnection connection) {
|
||||
|
@ -693,6 +759,9 @@ class Http2Connection {
|
|||
if (promisedStreamid != nextPushStream) {
|
||||
resetStream(promisedStreamid, ResetFrame.PROTOCOL_ERROR);
|
||||
return;
|
||||
} else if (!reserveStream(false)) {
|
||||
resetStream(promisedStreamid, ResetFrame.REFUSED_STREAM);
|
||||
return;
|
||||
} else {
|
||||
nextPushStream += 2;
|
||||
}
|
||||
|
@ -752,7 +821,7 @@ class Http2Connection {
|
|||
// corresponding entry in the window controller.
|
||||
windowController.removeStream(streamid);
|
||||
}
|
||||
if (singleStream() && streams.isEmpty()) {
|
||||
if (finalStream() && streams.isEmpty()) {
|
||||
// should be only 1 stream, but there might be more if server push
|
||||
close();
|
||||
}
|
||||
|
|
|
@ -28,12 +28,12 @@ package jdk.internal.net.http;
|
|||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLParameters;
|
||||
import java.io.IOException;
|
||||
import java.lang.System.Logger.Level;
|
||||
import java.lang.ref.Reference;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.net.Authenticator;
|
||||
import java.net.CookieHandler;
|
||||
import java.net.ProxySelector;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.CancelledKeyException;
|
||||
import java.nio.channels.ClosedChannelException;
|
||||
import java.nio.channels.SelectableChannel;
|
||||
|
@ -47,7 +47,6 @@ import java.security.PrivilegedAction;
|
|||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
|
@ -57,6 +56,7 @@ import java.util.Optional;
|
|||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
@ -70,6 +70,7 @@ import java.net.http.HttpResponse;
|
|||
import java.net.http.HttpResponse.BodyHandler;
|
||||
import java.net.http.HttpResponse.PushPromiseHandler;
|
||||
import java.net.http.WebSocket;
|
||||
import jdk.internal.net.http.common.BufferSupplier;
|
||||
import jdk.internal.net.http.common.Log;
|
||||
import jdk.internal.net.http.common.Logger;
|
||||
import jdk.internal.net.http.common.Pair;
|
||||
|
@ -138,6 +139,13 @@ final class HttpClientImpl extends HttpClient implements Trackable {
|
|||
private final long id;
|
||||
private final String dbgTag;
|
||||
|
||||
// The SSL DirectBuffer Supplier provides the ability to recycle
|
||||
// buffers used between the socket reader and the SSLEngine, or
|
||||
// more precisely between the SocketTube publisher and the
|
||||
// SSLFlowDelegate reader.
|
||||
private final SSLDirectBufferSupplier sslBufferSupplier
|
||||
= new SSLDirectBufferSupplier(this);
|
||||
|
||||
// This reference is used to keep track of the facade HttpClient
|
||||
// that was returned to the application code.
|
||||
// It makes it possible to know when the application no longer
|
||||
|
@ -1160,7 +1168,90 @@ final class HttpClientImpl extends HttpClient implements Trackable {
|
|||
// used for the connection window
|
||||
int getReceiveBufferSize() {
|
||||
return Utils.getIntegerNetProperty(
|
||||
"jdk.httpclient.receiveBufferSize", 2 * 1024 * 1024
|
||||
"jdk.httpclient.receiveBufferSize",
|
||||
0 // only set the size if > 0
|
||||
);
|
||||
}
|
||||
|
||||
// Optimization for reading SSL encrypted data
|
||||
// --------------------------------------------
|
||||
|
||||
// Returns a BufferSupplier that can be used for reading
|
||||
// encrypted bytes of the channel. These buffers can then
|
||||
// be recycled by the SSLFlowDelegate::Reader after their
|
||||
// content has been copied in the SSLFlowDelegate::Reader
|
||||
// readBuf.
|
||||
// Because allocating, reading, copying, and recycling
|
||||
// all happen in the SelectorManager thread,
|
||||
// then this BufferSupplier can be shared between all
|
||||
// the SSL connections managed by this client.
|
||||
BufferSupplier getSSLBufferSupplier() {
|
||||
return sslBufferSupplier;
|
||||
}
|
||||
|
||||
// An implementation of BufferSupplier that manages a pool of
|
||||
// maximum 3 direct byte buffers (SocketTube.MAX_BUFFERS) that
|
||||
// are used for reading encrypted bytes off the channel before
|
||||
// copying and subsequent unwrapping.
|
||||
private static final class SSLDirectBufferSupplier implements BufferSupplier {
|
||||
private static final int POOL_SIZE = SocketTube.MAX_BUFFERS;
|
||||
private final ByteBuffer[] pool = new ByteBuffer[POOL_SIZE];
|
||||
private final HttpClientImpl client;
|
||||
private final Logger debug;
|
||||
private int tail, count; // no need for volatile: only accessed in SM thread.
|
||||
|
||||
SSLDirectBufferSupplier(HttpClientImpl client) {
|
||||
this.client = Objects.requireNonNull(client);
|
||||
this.debug = client.debug;
|
||||
}
|
||||
|
||||
// Gets a buffer from the pool, or allocates a new one if needed.
|
||||
@Override
|
||||
public ByteBuffer get() {
|
||||
assert client.isSelectorThread();
|
||||
assert tail <= POOL_SIZE : "allocate tail is " + tail;
|
||||
ByteBuffer buf;
|
||||
if (tail == 0) {
|
||||
if (debug.on()) {
|
||||
// should not appear more than SocketTube.MAX_BUFFERS
|
||||
debug.log("ByteBuffer.allocateDirect(%d)", Utils.BUFSIZE);
|
||||
}
|
||||
assert count++ < POOL_SIZE : "trying to allocate more than "
|
||||
+ POOL_SIZE + " buffers";
|
||||
buf = ByteBuffer.allocateDirect(Utils.BUFSIZE);
|
||||
} else {
|
||||
assert tail > 0 : "non positive tail value: " + tail;
|
||||
tail--;
|
||||
buf = pool[tail];
|
||||
pool[tail] = null;
|
||||
}
|
||||
assert buf.isDirect();
|
||||
assert buf.position() == 0;
|
||||
assert buf.hasRemaining();
|
||||
assert buf.limit() == Utils.BUFSIZE;
|
||||
assert tail < POOL_SIZE;
|
||||
assert tail >= 0;
|
||||
return buf;
|
||||
}
|
||||
|
||||
// Returns the given buffer to the pool.
|
||||
@Override
|
||||
public void recycle(ByteBuffer buffer) {
|
||||
assert client.isSelectorThread();
|
||||
assert buffer.isDirect();
|
||||
assert !buffer.hasRemaining();
|
||||
assert tail < POOL_SIZE : "recycle tail is " + tail;
|
||||
assert tail >= 0;
|
||||
buffer.position(0);
|
||||
buffer.limit(buffer.capacity());
|
||||
// don't fail if assertions are off. we have asserted above.
|
||||
if (tail < POOL_SIZE) {
|
||||
pool[tail] = buffer;
|
||||
tail++;
|
||||
}
|
||||
assert tail <= POOL_SIZE;
|
||||
assert tail > 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -145,31 +145,39 @@ class PlainHttpConnection extends HttpConnection {
|
|||
try {
|
||||
this.chan = SocketChannel.open();
|
||||
chan.configureBlocking(false);
|
||||
int bufsize = client.getReceiveBufferSize();
|
||||
if (!trySetReceiveBufferSize(bufsize)) {
|
||||
trySetReceiveBufferSize(256*1024);
|
||||
trySetReceiveBufferSize(client.getReceiveBufferSize());
|
||||
if (debug.on()) {
|
||||
int bufsize = getInitialBufferSize();
|
||||
debug.log("Initial receive buffer size is: %d", bufsize);
|
||||
}
|
||||
chan.setOption(StandardSocketOptions.TCP_NODELAY, true);
|
||||
// wrap the connected channel in a Tube for async reading and writing
|
||||
// wrap the channel in a Tube for async reading and writing
|
||||
tube = new SocketTube(client(), chan, Utils::getBuffer);
|
||||
} catch (IOException e) {
|
||||
throw new InternalError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean trySetReceiveBufferSize(int bufsize) {
|
||||
private int getInitialBufferSize() {
|
||||
try {
|
||||
chan.setOption(StandardSocketOptions.SO_RCVBUF, bufsize);
|
||||
return chan.getOption(StandardSocketOptions.SO_RCVBUF);
|
||||
} catch(IOException x) {
|
||||
if (debug.on())
|
||||
debug.log("Receive buffer size is %s",
|
||||
chan.getOption(StandardSocketOptions.SO_RCVBUF));
|
||||
return true;
|
||||
debug.log("Failed to get initial receive buffer size on %s", chan);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void trySetReceiveBufferSize(int bufsize) {
|
||||
try {
|
||||
if (bufsize > 0) {
|
||||
chan.setOption(StandardSocketOptions.SO_RCVBUF, bufsize);
|
||||
}
|
||||
} catch(IOException x) {
|
||||
if (debug.on())
|
||||
debug.log("Failed to set receive buffer size to %d on %s",
|
||||
bufsize, chan);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -26,7 +26,6 @@
|
|||
package jdk.internal.net.http;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.System.Logger.Level;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
@ -39,6 +38,7 @@ import java.nio.channels.SocketChannel;
|
|||
import java.util.ArrayList;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
import jdk.internal.net.http.common.BufferSupplier;
|
||||
import jdk.internal.net.http.common.Demand;
|
||||
import jdk.internal.net.http.common.FlowTube;
|
||||
import jdk.internal.net.http.common.Logger;
|
||||
|
@ -59,7 +59,7 @@ final class SocketTube implements FlowTube {
|
|||
|
||||
private final HttpClientImpl client;
|
||||
private final SocketChannel channel;
|
||||
private final Supplier<ByteBuffer> buffersSource;
|
||||
private final SliceBufferSource sliceBuffersSource;
|
||||
private final Object lock = new Object();
|
||||
private final AtomicReference<Throwable> errorRef = new AtomicReference<>();
|
||||
private final InternalReadPublisher readPublisher;
|
||||
|
@ -67,10 +67,11 @@ final class SocketTube implements FlowTube {
|
|||
private final long id = IDS.incrementAndGet();
|
||||
|
||||
public SocketTube(HttpClientImpl client, SocketChannel channel,
|
||||
Supplier<ByteBuffer> buffersSource) {
|
||||
Supplier<ByteBuffer> buffersFactory) {
|
||||
this.client = client;
|
||||
this.channel = channel;
|
||||
this.buffersSource = buffersSource;
|
||||
this.sliceBuffersSource = new SliceBufferSource(buffersFactory);
|
||||
|
||||
this.readPublisher = new InternalReadPublisher();
|
||||
this.writeSubscriber = new InternalWriteSubscriber();
|
||||
}
|
||||
|
@ -564,6 +565,7 @@ final class SocketTube implements FlowTube {
|
|||
final InternalReadSubscription impl;
|
||||
final TubeSubscriber subscriber;
|
||||
final AtomicReference<Throwable> errorRef = new AtomicReference<>();
|
||||
final BufferSource bufferSource;
|
||||
volatile boolean subscribed;
|
||||
volatile boolean cancelled;
|
||||
volatile boolean completed;
|
||||
|
@ -571,6 +573,9 @@ final class SocketTube implements FlowTube {
|
|||
public ReadSubscription(InternalReadSubscription impl,
|
||||
TubeSubscriber subscriber) {
|
||||
this.impl = impl;
|
||||
this.bufferSource = subscriber.supportsRecycling()
|
||||
? new SSLDirectBufferSource(client)
|
||||
: SocketTube.this.sliceBuffersSource;
|
||||
this.subscriber = subscriber;
|
||||
}
|
||||
|
||||
|
@ -779,7 +784,7 @@ final class SocketTube implements FlowTube {
|
|||
if (demand.tryDecrement()) {
|
||||
// we have demand.
|
||||
try {
|
||||
List<ByteBuffer> bytes = readAvailable();
|
||||
List<ByteBuffer> bytes = readAvailable(current.bufferSource);
|
||||
if (bytes == EOF) {
|
||||
if (!completed) {
|
||||
if (debug.on()) debug.log("got read EOF");
|
||||
|
@ -905,6 +910,180 @@ final class SocketTube implements FlowTube {
|
|||
}
|
||||
}
|
||||
|
||||
// ===================================================================== //
|
||||
// Buffer Management //
|
||||
// ===================================================================== //
|
||||
|
||||
// This interface is used by readAvailable(BufferSource);
|
||||
public interface BufferSource {
|
||||
/**
|
||||
* Returns a buffer to read data from the socket.
|
||||
*
|
||||
* @implNote
|
||||
* Different implementation can have different strategies, as to
|
||||
* which kind of buffer to return, or whether to return the same
|
||||
* buffer. The only constraints are that:
|
||||
* a. the buffer returned must not be null
|
||||
* b. the buffer position indicates where to start reading
|
||||
* c. the buffer limit indicates where to stop reading.
|
||||
* d. the buffer is 'free' - that is - it is not used
|
||||
* or retained by anybody else
|
||||
*
|
||||
* @return A buffer to read data from the socket.
|
||||
*/
|
||||
ByteBuffer getBuffer();
|
||||
|
||||
/**
|
||||
* Appends the read-data in {@code buffer} to the list of buffer to
|
||||
* be sent downstream to the subscriber. May return a new
|
||||
* list, or append to the given list.
|
||||
*
|
||||
* @implNote
|
||||
* Different implementation can have different strategies, but
|
||||
* must obviously be consistent with the implementation of the
|
||||
* getBuffer() method. For instance, an implementation could
|
||||
* decide to add the buffer to the list and return a new buffer
|
||||
* next time getBuffer() is called, or could decide to add a buffer
|
||||
* slice to the list and return the same buffer (if remaining
|
||||
* space is available) next time getBuffer() is called.
|
||||
*
|
||||
* @param list The list before adding the data. Can be null.
|
||||
* @param buffer The buffer containing the data to add to the list.
|
||||
* @param start The start position at which data were read.
|
||||
* The current buffer position indicates the end.
|
||||
* @return A possibly new list where a buffer containing the
|
||||
* data read from the socket has been added.
|
||||
*/
|
||||
List<ByteBuffer> append(List<ByteBuffer> list, ByteBuffer buffer, int start);
|
||||
|
||||
/**
|
||||
* Returns the given unused {@code buffer}, previously obtained from
|
||||
* {@code getBuffer}.
|
||||
*
|
||||
* @implNote This method can be used, if necessary, to return
|
||||
* the unused buffer to the pull.
|
||||
*
|
||||
* @param buffer The unused buffer.
|
||||
*/
|
||||
default void returnUnused(ByteBuffer buffer) { }
|
||||
}
|
||||
|
||||
// An implementation of BufferSource used for unencrypted data.
|
||||
// This buffer source uses heap buffers and avoids wasting memory
|
||||
// by forwarding read-only buffer slices downstream.
|
||||
// Buffers allocated through this source are simply GC'ed when
|
||||
// they are no longer referenced.
|
||||
private static final class SliceBufferSource implements BufferSource {
|
||||
private final Supplier<ByteBuffer> factory;
|
||||
private volatile ByteBuffer current;
|
||||
|
||||
public SliceBufferSource() {
|
||||
this(Utils::getBuffer);
|
||||
}
|
||||
public SliceBufferSource(Supplier<ByteBuffer> factory) {
|
||||
this.factory = Objects.requireNonNull(factory);
|
||||
}
|
||||
|
||||
// Reuses the same buffer if some space remains available.
|
||||
// Otherwise, returns a new heap buffer.
|
||||
@Override
|
||||
public final ByteBuffer getBuffer() {
|
||||
ByteBuffer buf = current;
|
||||
buf = (buf == null || !buf.hasRemaining())
|
||||
? (current = factory.get()) : buf;
|
||||
assert buf.hasRemaining();
|
||||
return buf;
|
||||
}
|
||||
|
||||
// Adds a read-only slice to the list, potentially returning a
|
||||
// new list with that slice at the end.
|
||||
@Override
|
||||
public final List<ByteBuffer> append(List <ByteBuffer> list, ByteBuffer buf, int start) {
|
||||
// creates a slice to add to the list
|
||||
int limit = buf.limit();
|
||||
buf.limit(buf.position());
|
||||
buf.position(start);
|
||||
ByteBuffer slice = buf.slice();
|
||||
|
||||
// restore buffer state to what it was before creating the slice
|
||||
buf.position(buf.limit());
|
||||
buf.limit(limit);
|
||||
|
||||
// add the buffer to the list
|
||||
return SocketTube.listOf(list, slice.asReadOnlyBuffer());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// An implementation of BufferSource used for encrypted data.
|
||||
// This buffer source uses direct byte buffers that will be
|
||||
// recycled by the SocketTube subscriber.
|
||||
//
|
||||
private static final class SSLDirectBufferSource implements BufferSource {
|
||||
private final BufferSupplier factory;
|
||||
private final HttpClientImpl client;
|
||||
private ByteBuffer current;
|
||||
|
||||
public SSLDirectBufferSource(HttpClientImpl client) {
|
||||
this.client = Objects.requireNonNull(client);
|
||||
this.factory = Objects.requireNonNull(client.getSSLBufferSupplier());
|
||||
}
|
||||
|
||||
// Obtains a 'free' byte buffer from the pool, or returns
|
||||
// the same buffer if nothing was read at the previous cycle.
|
||||
// The subscriber will be responsible for recycling this
|
||||
// buffer into the pool (see SSLFlowDelegate.Reader)
|
||||
@Override
|
||||
public final ByteBuffer getBuffer() {
|
||||
assert client.isSelectorThread();
|
||||
ByteBuffer buf = current;
|
||||
if (buf == null) {
|
||||
buf = current = factory.get();
|
||||
}
|
||||
assert buf.hasRemaining();
|
||||
assert buf.position() == 0;
|
||||
return buf;
|
||||
}
|
||||
|
||||
// Adds the buffer to the list. The buffer will be later returned to the
|
||||
// pool by the subscriber (see SSLFlowDelegate.Reader).
|
||||
// The next buffer returned by getBuffer() will be obtained from the
|
||||
// pool. It might be the same buffer or another one.
|
||||
// Because socket tube can read up to MAX_BUFFERS = 3 buffers, and because
|
||||
// recycling will happen in the flow before onNext returns, then the
|
||||
// pool can not grow larger than MAX_BUFFERS = 3 buffers, even though
|
||||
// it's shared by all SSL connections opened on that client.
|
||||
@Override
|
||||
public final List<ByteBuffer> append(List <ByteBuffer> list, ByteBuffer buf, int start) {
|
||||
assert client.isSelectorThread();
|
||||
assert buf.isDirect();
|
||||
assert start == 0;
|
||||
assert current == buf;
|
||||
current = null;
|
||||
buf.limit(buf.position());
|
||||
buf.position(start);
|
||||
// add the buffer to the list
|
||||
return SocketTube.listOf(list, buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void returnUnused(ByteBuffer buffer) {
|
||||
// if current is null, then the buffer will have been added to the
|
||||
// list, through append. Otherwise, current is not null, and needs
|
||||
// to be returned to prevent the buffer supplier pool from growing
|
||||
// to more than MAX_BUFFERS.
|
||||
assert buffer == current;
|
||||
ByteBuffer buf = current;
|
||||
if (buf != null) {
|
||||
assert buf.position() == 0;
|
||||
current = null;
|
||||
// the supplier assert if buf has remaining
|
||||
buf.limit(buf.position());
|
||||
factory.recycle(buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================================================== //
|
||||
// Socket Channel Read/Write //
|
||||
// ===================================================================== //
|
||||
|
@ -918,11 +1097,8 @@ final class SocketTube implements FlowTube {
|
|||
// is inserted into the returned buffer list, and if the current buffer
|
||||
// has remaining space, that space will be used to read more data when
|
||||
// the channel becomes readable again.
|
||||
private volatile ByteBuffer current;
|
||||
private List<ByteBuffer> readAvailable() throws IOException {
|
||||
ByteBuffer buf = current;
|
||||
buf = (buf == null || !buf.hasRemaining())
|
||||
? (current = buffersSource.get()) : buf;
|
||||
private List<ByteBuffer> readAvailable(BufferSource buffersSource) throws IOException {
|
||||
ByteBuffer buf = buffersSource.getBuffer();
|
||||
assert buf.hasRemaining();
|
||||
|
||||
int read;
|
||||
|
@ -936,6 +1112,9 @@ final class SocketTube implements FlowTube {
|
|||
}
|
||||
} catch (IOException x) {
|
||||
if (buf.position() == pos && list == null) {
|
||||
// make sure that the buffer source will recycle
|
||||
// 'buf' if needed
|
||||
buffersSource.returnUnused(buf);
|
||||
// no bytes have been read, just throw...
|
||||
throw x;
|
||||
} else {
|
||||
|
@ -951,6 +1130,7 @@ final class SocketTube implements FlowTube {
|
|||
// returned if read == -1. If some data has already been read,
|
||||
// then it must be returned. -1 will be returned next time
|
||||
// the caller attempts to read something.
|
||||
buffersSource.returnUnused(buf);
|
||||
if (list == null) {
|
||||
// nothing read - list was null - return EOF or NOTHING
|
||||
list = read == -1 ? EOF : NOTHING;
|
||||
|
@ -960,39 +1140,27 @@ final class SocketTube implements FlowTube {
|
|||
|
||||
// check whether this buffer has still some free space available.
|
||||
// if so, we will keep it for the next round.
|
||||
final boolean hasRemaining = buf.hasRemaining();
|
||||
list = buffersSource.append(list, buf, pos);
|
||||
|
||||
// creates a slice to add to the list
|
||||
int limit = buf.limit();
|
||||
buf.limit(buf.position());
|
||||
buf.position(pos);
|
||||
ByteBuffer slice = buf.slice();
|
||||
|
||||
// restore buffer state to what it was before creating the slice
|
||||
buf.position(buf.limit());
|
||||
buf.limit(limit);
|
||||
|
||||
// add the buffer to the list
|
||||
list = addToList(list, slice.asReadOnlyBuffer());
|
||||
if (read <= 0 || list.size() == MAX_BUFFERS) {
|
||||
break;
|
||||
}
|
||||
|
||||
buf = hasRemaining ? buf : (current = buffersSource.get());
|
||||
buf = buffersSource.getBuffer();
|
||||
pos = buf.position();
|
||||
assert buf.hasRemaining();
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private <T> List<T> addToList(List<T> list, T item) {
|
||||
private static <T> List<T> listOf(List<T> list, T item) {
|
||||
int size = list == null ? 0 : list.size();
|
||||
switch (size) {
|
||||
case 0: return List.of(item);
|
||||
case 1: return List.of(list.get(0), item);
|
||||
case 2: return List.of(list.get(0), list.get(1), item);
|
||||
default: // slow path if MAX_BUFFERS > 3
|
||||
ArrayList<T> res = new ArrayList<>(list);
|
||||
List<T> res = list instanceof ArrayList ? list : new ArrayList<>(list);
|
||||
res.add(item);
|
||||
return res;
|
||||
}
|
||||
|
|
|
@ -614,7 +614,7 @@ class Stream<T> extends ExchangeImpl<T> {
|
|||
if (query != null) {
|
||||
path += "?" + query;
|
||||
}
|
||||
hdrs.setHeader(":path", path);
|
||||
hdrs.setHeader(":path", Utils.encode(path));
|
||||
}
|
||||
|
||||
HttpHeadersImpl getRequestPseudoHeaders() {
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.internal.net.http.common;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* This interface allows to recycle buffers used for SSL decryption.
|
||||
* Buffers that are used for reading SSL encrypted data are typically
|
||||
* very short lived, as it is necessary to aggregate their content
|
||||
* before calling SSLEngine::unwrap.
|
||||
* Because both reading and copying happen in the SelectorManager
|
||||
* thread, then it makes it possible to pool these buffers and
|
||||
* recycle them for the next socket read, instead of simply
|
||||
* letting them be GC'ed. That also makes it possible to use
|
||||
* direct byte buffers, and avoid another layer of copying by
|
||||
* the SocketChannel implementation.
|
||||
*
|
||||
* The HttpClientImpl has an implementation of this interface
|
||||
* that allows to reuse the same 3 direct buffers for reading
|
||||
* off SSL encrypted data from the socket.
|
||||
* The BufferSupplier::get method is called by SocketTube
|
||||
* (see SocketTube.SSLDirectBufferSource) and BufferSupplier::recycle
|
||||
* is called by SSLFlowDelegate.Reader.
|
||||
**/
|
||||
public interface BufferSupplier extends Supplier<ByteBuffer> {
|
||||
/**
|
||||
* Returns a buffer to read encrypted data off the socket.
|
||||
* @return a buffer to read encrypted data off the socket.
|
||||
*/
|
||||
ByteBuffer get();
|
||||
|
||||
/**
|
||||
* Returns a buffer to the pool.
|
||||
*
|
||||
* @param buffer This must be a buffer previously obtained
|
||||
* by calling BufferSupplier::get. The caller must
|
||||
* not touch the buffer after returning it to
|
||||
* the pool.
|
||||
*/
|
||||
void recycle(ByteBuffer buffer);
|
||||
}
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.internal.net.http.common;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
/**
|
||||
* The class provides reuse of ByteBuffers.
|
||||
* It is supposed that all requested buffers have the same size for a long period of time.
|
||||
* That is why there is no any logic splitting buffers into different buckets (by size). It's unnecessary.
|
||||
*
|
||||
* At the same moment it is allowed to change requested buffers size (all smaller buffers will be discarded).
|
||||
* It may be needed for example, if after rehandshaking netPacketBufferSize was changed.
|
||||
*/
|
||||
public class ByteBufferPool {
|
||||
|
||||
private final java.util.Queue<ByteBuffer> pool = new ConcurrentLinkedQueue<>();
|
||||
|
||||
public ByteBufferPool() {
|
||||
}
|
||||
|
||||
public ByteBufferReference get(int size) {
|
||||
ByteBuffer buffer;
|
||||
while ((buffer = pool.poll()) != null) {
|
||||
if (buffer.capacity() >= size) {
|
||||
return ByteBufferReference.of(buffer, this);
|
||||
}
|
||||
}
|
||||
return ByteBufferReference.of(ByteBuffer.allocate(size), this);
|
||||
}
|
||||
|
||||
public void release(ByteBuffer buffer) {
|
||||
buffer.clear();
|
||||
pool.offer(buffer);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.internal.net.http.common;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class ByteBufferReference implements Supplier<ByteBuffer> {
|
||||
|
||||
private ByteBuffer buffer;
|
||||
private final ByteBufferPool pool;
|
||||
|
||||
public static ByteBufferReference of(ByteBuffer buffer) {
|
||||
return of(buffer, null);
|
||||
}
|
||||
|
||||
public static ByteBufferReference of(ByteBuffer buffer, ByteBufferPool pool) {
|
||||
Objects.requireNonNull(buffer);
|
||||
return new ByteBufferReference(buffer, pool);
|
||||
}
|
||||
|
||||
public static ByteBuffer[] toBuffers(ByteBufferReference... refs) {
|
||||
ByteBuffer[] bufs = new ByteBuffer[refs.length];
|
||||
for (int i = 0; i < refs.length; i++) {
|
||||
bufs[i] = refs[i].get();
|
||||
}
|
||||
return bufs;
|
||||
}
|
||||
|
||||
public static ByteBufferReference[] toReferences(ByteBuffer... buffers) {
|
||||
ByteBufferReference[] refs = new ByteBufferReference[buffers.length];
|
||||
for (int i = 0; i < buffers.length; i++) {
|
||||
refs[i] = of(buffers[i]);
|
||||
}
|
||||
return refs;
|
||||
}
|
||||
|
||||
|
||||
public static void clear(ByteBufferReference[] refs) {
|
||||
for(ByteBufferReference ref : refs) {
|
||||
ref.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private ByteBufferReference(ByteBuffer buffer, ByteBufferPool pool) {
|
||||
this.buffer = buffer;
|
||||
this.pool = pool;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer get() {
|
||||
ByteBuffer buf = this.buffer;
|
||||
assert buf!=null : "getting ByteBuffer after clearance";
|
||||
return buf;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
ByteBuffer buf = this.buffer;
|
||||
assert buf!=null : "double ByteBuffer clearance";
|
||||
this.buffer = null;
|
||||
if (pool != null) {
|
||||
pool.release(buf);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -52,6 +52,12 @@ final class DebugLogger implements Logger {
|
|||
final static System.Logger HTTP = System.getLogger(HTTP_NAME);
|
||||
final static System.Logger WS = System.getLogger(WS_NAME);
|
||||
final static System.Logger HPACK = System.getLogger(HPACK_NAME);
|
||||
private static final DebugLogger NO_HTTP_LOGGER =
|
||||
new DebugLogger(HTTP, "HTTP"::toString, Level.OFF, Level.OFF);
|
||||
private static final DebugLogger NO_WS_LOGGER =
|
||||
new DebugLogger(HTTP, "WS"::toString, Level.OFF, Level.OFF);
|
||||
private static final DebugLogger NO_HPACK_LOGGER =
|
||||
new DebugLogger(HTTP, "HPACK"::toString, Level.OFF, Level.OFF);
|
||||
final static long START_NANOS = System.nanoTime();
|
||||
|
||||
private final Supplier<String> dbgTag;
|
||||
|
@ -112,11 +118,7 @@ final class DebugLogger implements Logger {
|
|||
}
|
||||
|
||||
private boolean isEnabled(Level level) {
|
||||
if (level == Level.OFF) return false;
|
||||
int severity = level.getSeverity();
|
||||
return severity >= errLevel.getSeverity()
|
||||
|| severity >= outLevel.getSeverity()
|
||||
|| logger.isLoggable(level);
|
||||
return levelEnabledFor(level, outLevel, errLevel, logger);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -124,6 +126,15 @@ final class DebugLogger implements Logger {
|
|||
return debugOn;
|
||||
}
|
||||
|
||||
static boolean levelEnabledFor(Level level, Level outLevel, Level errLevel,
|
||||
System.Logger logger) {
|
||||
if (level == Level.OFF) return false;
|
||||
int severity = level.getSeverity();
|
||||
return severity >= errLevel.getSeverity()
|
||||
|| severity >= outLevel.getSeverity()
|
||||
|| logger.isLoggable(level);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLoggable(Level level) {
|
||||
// fast path, we assume these guys never change.
|
||||
|
@ -251,18 +262,33 @@ final class DebugLogger implements Logger {
|
|||
public static DebugLogger createHttpLogger(Supplier<String> dbgTag,
|
||||
Level outLevel,
|
||||
Level errLevel) {
|
||||
return new DebugLogger(HTTP, dbgTag, outLevel, errLevel);
|
||||
if (levelEnabledFor(Level.DEBUG, outLevel, errLevel, HTTP)) {
|
||||
return new DebugLogger(HTTP, dbgTag, outLevel, errLevel);
|
||||
} else {
|
||||
// return a shared instance if debug logging is not enabled.
|
||||
return NO_HTTP_LOGGER;
|
||||
}
|
||||
}
|
||||
|
||||
public static DebugLogger createWebSocketLogger(Supplier<String> dbgTag,
|
||||
Level outLevel,
|
||||
Level errLevel) {
|
||||
return new DebugLogger(WS, dbgTag, outLevel, errLevel);
|
||||
if (levelEnabledFor(Level.DEBUG, outLevel, errLevel, WS)) {
|
||||
return new DebugLogger(WS, dbgTag, outLevel, errLevel);
|
||||
} else {
|
||||
// return a shared instance if logging is not enabled.
|
||||
return NO_WS_LOGGER;
|
||||
}
|
||||
}
|
||||
|
||||
public static DebugLogger createHpackLogger(Supplier<String> dbgTag,
|
||||
Level outLevel,
|
||||
Level errLevel) {
|
||||
return new DebugLogger(HPACK, dbgTag, outLevel, errLevel);
|
||||
if (levelEnabledFor(Level.DEBUG, outLevel, errLevel, HPACK)) {
|
||||
return new DebugLogger(HPACK, dbgTag, outLevel, errLevel);
|
||||
} else {
|
||||
// return a shared instance if logging is not enabled.
|
||||
return NO_HPACK_LOGGER;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,6 +63,8 @@ public interface FlowTube extends
|
|||
*/
|
||||
default void dropSubscription() { }
|
||||
|
||||
default boolean supportsRecycling() { return false; }
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -33,7 +33,6 @@ import javax.net.ssl.SSLEngineResult.HandshakeStatus;
|
|||
import javax.net.ssl.SSLEngineResult.Status;
|
||||
import javax.net.ssl.SSLException;
|
||||
import java.io.IOException;
|
||||
import java.lang.System.Logger.Level;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
@ -46,6 +45,7 @@ import java.util.concurrent.Executor;
|
|||
import java.util.concurrent.Flow;
|
||||
import java.util.concurrent.Flow.Subscriber;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Implements SSL using two SubscriberWrappers.
|
||||
|
@ -97,6 +97,7 @@ public class SSLFlowDelegate {
|
|||
volatile boolean close_notify_received;
|
||||
final CompletableFuture<Void> readerCF;
|
||||
final CompletableFuture<Void> writerCF;
|
||||
final Consumer<ByteBuffer> recycler;
|
||||
static AtomicInteger scount = new AtomicInteger(1);
|
||||
final int id;
|
||||
|
||||
|
@ -110,8 +111,23 @@ public class SSLFlowDelegate {
|
|||
Subscriber<? super List<ByteBuffer>> downReader,
|
||||
Subscriber<? super List<ByteBuffer>> downWriter)
|
||||
{
|
||||
this(engine, exec, null, downReader, downWriter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an SSLFlowDelegate fed from two Flow.Subscribers. Each
|
||||
* Flow.Subscriber requires an associated {@link CompletableFuture}
|
||||
* for errors that need to be signaled from downstream to upstream.
|
||||
*/
|
||||
public SSLFlowDelegate(SSLEngine engine,
|
||||
Executor exec,
|
||||
Consumer<ByteBuffer> recycler,
|
||||
Subscriber<? super List<ByteBuffer>> downReader,
|
||||
Subscriber<? super List<ByteBuffer>> downWriter)
|
||||
{
|
||||
this.id = scount.getAndIncrement();
|
||||
this.tubeName = String.valueOf(downWriter);
|
||||
this.recycler = recycler;
|
||||
this.reader = new Reader();
|
||||
this.writer = new Writer();
|
||||
this.engine = engine;
|
||||
|
@ -181,9 +197,11 @@ public class SSLFlowDelegate {
|
|||
sb.append("SSL: id ").append(id);
|
||||
sb.append(" HS state: " + states(handshakeState));
|
||||
sb.append(" Engine state: " + engine.getHandshakeStatus().toString());
|
||||
sb.append(" LL : ");
|
||||
for (String s: stateList) {
|
||||
sb.append(s).append(" ");
|
||||
if (stateList != null) {
|
||||
sb.append(" LL : ");
|
||||
for (String s : stateList) {
|
||||
sb.append(s).append(" ");
|
||||
}
|
||||
}
|
||||
sb.append("\r\n");
|
||||
sb.append("Reader:: ").append(reader.toString());
|
||||
|
@ -213,15 +231,20 @@ public class SSLFlowDelegate {
|
|||
* Upstream subscription strategy is to try and keep no more than
|
||||
* TARGET_BUFSIZE bytes in readBuf
|
||||
*/
|
||||
class Reader extends SubscriberWrapper {
|
||||
final SequentialScheduler scheduler;
|
||||
final class Reader extends SubscriberWrapper implements FlowTube.TubeSubscriber {
|
||||
// Maximum record size is 16k.
|
||||
// Because SocketTube can feeds us up to 3 16K buffers,
|
||||
// then setting this size to 16K means that the readBuf
|
||||
// can store up to 64K-1 (16K-1 + 3*16K)
|
||||
static final int TARGET_BUFSIZE = 16 * 1024;
|
||||
|
||||
final SequentialScheduler scheduler;
|
||||
volatile ByteBuffer readBuf;
|
||||
volatile boolean completing;
|
||||
final Object readBufferLock = new Object();
|
||||
final Logger debugr = Utils.getDebugLogger(this::dbgString, Utils.DEBUG);
|
||||
|
||||
class ReaderDownstreamPusher implements Runnable {
|
||||
private final class ReaderDownstreamPusher implements Runnable {
|
||||
@Override public void run() { processData(); }
|
||||
}
|
||||
|
||||
|
@ -233,6 +256,11 @@ public class SSLFlowDelegate {
|
|||
readBuf.limit(0); // keep in read mode
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRecycling() {
|
||||
return recycler != null;
|
||||
}
|
||||
|
||||
protected SchedulingAction enterScheduling() {
|
||||
return enterReadScheduling();
|
||||
}
|
||||
|
@ -250,7 +278,7 @@ public class SSLFlowDelegate {
|
|||
debugr.log("Adding %d bytes to read buffer",
|
||||
Utils.remaining(buffers));
|
||||
addToReadBuf(buffers, complete);
|
||||
scheduler.runOrSchedule();
|
||||
scheduler.runOrSchedule(exec);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -270,6 +298,9 @@ public class SSLFlowDelegate {
|
|||
@Override
|
||||
protected long upstreamWindowUpdate(long currentWindow, long downstreamQsize) {
|
||||
if (readBuf.remaining() > TARGET_BUFSIZE) {
|
||||
if (debugr.on())
|
||||
debugr.log("readBuf has more than TARGET_BUFSIZE: %d",
|
||||
readBuf.remaining());
|
||||
return 0;
|
||||
} else {
|
||||
return super.upstreamWindowUpdate(currentWindow, downstreamQsize);
|
||||
|
@ -285,6 +316,11 @@ public class SSLFlowDelegate {
|
|||
reallocReadBuf();
|
||||
readBuf.put(buf);
|
||||
readBuf.flip();
|
||||
// should be safe to call inside lock
|
||||
// since the only implementation
|
||||
// offers the buffer to an unbounded queue.
|
||||
// WARNING: do not touch buf after this point!
|
||||
if (recycler != null) recycler.accept(buf);
|
||||
}
|
||||
if (complete) {
|
||||
this.completing = complete;
|
||||
|
@ -293,7 +329,7 @@ public class SSLFlowDelegate {
|
|||
}
|
||||
|
||||
void schedule() {
|
||||
scheduler.runOrSchedule();
|
||||
scheduler.runOrSchedule(exec);
|
||||
}
|
||||
|
||||
void stop() {
|
||||
|
@ -303,8 +339,13 @@ public class SSLFlowDelegate {
|
|||
|
||||
AtomicInteger count = new AtomicInteger(0);
|
||||
|
||||
// minimum number of bytes required to call unwrap.
|
||||
// Usually this is 0, unless there was a buffer underflow.
|
||||
// In this case we need to wait for more bytes than what
|
||||
// we had before calling unwrap() again.
|
||||
volatile int minBytesRequired;
|
||||
// work function where it all happens
|
||||
void processData() {
|
||||
final void processData() {
|
||||
try {
|
||||
if (debugr.on())
|
||||
debugr.log("processData:"
|
||||
|
@ -313,15 +354,23 @@ public class SSLFlowDelegate {
|
|||
+ ", engine handshake status:" + engine.getHandshakeStatus());
|
||||
int len;
|
||||
boolean complete = false;
|
||||
while ((len = readBuf.remaining()) > 0) {
|
||||
while (readBuf.remaining() > (len = minBytesRequired)) {
|
||||
boolean handshaking = false;
|
||||
try {
|
||||
EngineResult result;
|
||||
synchronized (readBufferLock) {
|
||||
complete = this.completing;
|
||||
if (debugr.on()) debugr.log("Unwrapping: %s", readBuf.remaining());
|
||||
// Unless there is a BUFFER_UNDERFLOW, we should try to
|
||||
// unwrap any number of bytes. Set minBytesRequired to 0:
|
||||
// we only need to do that if minBytesRequired is not already 0.
|
||||
len = len > 0 ? minBytesRequired = 0 : len;
|
||||
result = unwrapBuffer(readBuf);
|
||||
if (debugr.on())
|
||||
debugr.log("Unwrapped: %s", result.result);
|
||||
len = readBuf.remaining();
|
||||
if (debugr.on()) {
|
||||
debugr.log("Unwrapped: result: %s", result.result);
|
||||
debugr.log("Unwrapped: consumed: %s", result.bytesConsumed());
|
||||
}
|
||||
}
|
||||
if (result.bytesProduced() > 0) {
|
||||
if (debugr.on())
|
||||
|
@ -332,12 +381,19 @@ public class SSLFlowDelegate {
|
|||
if (result.status() == Status.BUFFER_UNDERFLOW) {
|
||||
if (debugr.on()) debugr.log("BUFFER_UNDERFLOW");
|
||||
// not enough data in the read buffer...
|
||||
requestMore();
|
||||
// no need to try to unwrap again unless we get more bytes
|
||||
// than minBytesRequired = len in the read buffer.
|
||||
minBytesRequired = len;
|
||||
synchronized (readBufferLock) {
|
||||
// check if we have received some data
|
||||
// more bytes could already have been added...
|
||||
assert readBuf.remaining() >= len;
|
||||
// check if we have received some data, and if so
|
||||
// we can just re-spin the loop
|
||||
if (readBuf.remaining() > len) continue;
|
||||
return;
|
||||
}
|
||||
// request more data and return.
|
||||
requestMore();
|
||||
return;
|
||||
}
|
||||
if (complete && result.status() == Status.CLOSED) {
|
||||
if (debugr.on()) debugr.log("Closed: completing");
|
||||
|
@ -352,8 +408,10 @@ public class SSLFlowDelegate {
|
|||
handshaking = true;
|
||||
} else {
|
||||
if ((handshakeState.getAndSet(NOT_HANDSHAKING)& ~DOING_TASKS) == HANDSHAKING) {
|
||||
setALPN();
|
||||
handshaking = false;
|
||||
applicationBufferSize = engine.getSession().getApplicationBufferSize();
|
||||
packetBufferSize = engine.getSession().getPacketBufferSize();
|
||||
setALPN();
|
||||
resumeActivity();
|
||||
}
|
||||
}
|
||||
|
@ -391,7 +449,8 @@ public class SSLFlowDelegate {
|
|||
case BUFFER_OVERFLOW:
|
||||
// may happen only if app size buffer was changed.
|
||||
// get it again if app buffer size changed
|
||||
int appSize = engine.getSession().getApplicationBufferSize();
|
||||
int appSize = applicationBufferSize =
|
||||
engine.getSession().getApplicationBufferSize();
|
||||
ByteBuffer b = ByteBuffer.allocate(appSize + dst.position());
|
||||
dst.flip();
|
||||
b.put(dst);
|
||||
|
@ -489,7 +548,7 @@ public class SSLFlowDelegate {
|
|||
|
||||
@Override
|
||||
protected void incoming(List<ByteBuffer> buffers, boolean complete) {
|
||||
assert complete ? buffers == Utils.EMPTY_BB_LIST : true;
|
||||
assert complete ? buffers == Utils.EMPTY_BB_LIST : true;
|
||||
assert buffers != Utils.EMPTY_BB_LIST ? complete == false : true;
|
||||
if (complete) {
|
||||
if (debugw.on()) debugw.log("adding SENTINEL");
|
||||
|
@ -549,6 +608,15 @@ public class SSLFlowDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
void triggerWrite() {
|
||||
synchronized (writeList) {
|
||||
if (writeList.isEmpty()) {
|
||||
writeList.add(HS_TRIGGER);
|
||||
}
|
||||
}
|
||||
scheduler.runOrSchedule();
|
||||
}
|
||||
|
||||
private void processData() {
|
||||
boolean completing = isCompleting();
|
||||
|
||||
|
@ -586,6 +654,8 @@ public class SSLFlowDelegate {
|
|||
handshaking = true;
|
||||
} else {
|
||||
if ((handshakeState.getAndSet(NOT_HANDSHAKING) & ~DOING_TASKS) == HANDSHAKING) {
|
||||
applicationBufferSize = engine.getSession().getApplicationBufferSize();
|
||||
packetBufferSize = engine.getSession().getPacketBufferSize();
|
||||
setALPN();
|
||||
resumeActivity();
|
||||
}
|
||||
|
@ -630,8 +700,9 @@ public class SSLFlowDelegate {
|
|||
// Shouldn't happen. We allocated buffer with packet size
|
||||
// get it again if net buffer size was changed
|
||||
if (debugw.on()) debugw.log("BUFFER_OVERFLOW");
|
||||
int appSize = engine.getSession().getApplicationBufferSize();
|
||||
ByteBuffer b = ByteBuffer.allocate(appSize + dst.position());
|
||||
int netSize = packetBufferSize
|
||||
= engine.getSession().getPacketBufferSize();
|
||||
ByteBuffer b = ByteBuffer.allocate(netSize + dst.position());
|
||||
dst.flip();
|
||||
b.put(dst);
|
||||
dst = b;
|
||||
|
@ -759,13 +830,16 @@ public class SSLFlowDelegate {
|
|||
}
|
||||
|
||||
final AtomicInteger handshakeState;
|
||||
final ConcurrentLinkedQueue<String> stateList = new ConcurrentLinkedQueue<>();
|
||||
final ConcurrentLinkedQueue<String> stateList =
|
||||
debug.on() ? new ConcurrentLinkedQueue<>() : null;
|
||||
|
||||
private boolean doHandshake(EngineResult r, int caller) {
|
||||
// unconditionally sets the HANDSHAKING bit, while preserving DOING_TASKS
|
||||
handshakeState.getAndAccumulate(HANDSHAKING, (current, update) -> update | (current & DOING_TASKS));
|
||||
stateList.add(r.handshakeStatus().toString());
|
||||
stateList.add(Integer.toString(caller));
|
||||
if (stateList != null && debug.on()) {
|
||||
stateList.add(r.handshakeStatus().toString());
|
||||
stateList.add(Integer.toString(caller));
|
||||
}
|
||||
switch (r.handshakeStatus()) {
|
||||
case NEED_TASK:
|
||||
int s = handshakeState.getAndUpdate((current) -> current | DOING_TASKS);
|
||||
|
@ -778,7 +852,7 @@ public class SSLFlowDelegate {
|
|||
return false; // executeTasks will resume activity
|
||||
case NEED_WRAP:
|
||||
if (caller == READER) {
|
||||
writer.addData(HS_TRIGGER);
|
||||
writer.triggerWrite();
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
@ -818,7 +892,6 @@ public class SSLFlowDelegate {
|
|||
}
|
||||
} while (true);
|
||||
handshakeState.getAndUpdate((current) -> current & ~DOING_TASKS);
|
||||
//writer.addData(HS_TRIGGER);
|
||||
resumeActivity();
|
||||
} catch (Throwable t) {
|
||||
handleError(t);
|
||||
|
@ -839,7 +912,17 @@ public class SSLFlowDelegate {
|
|||
if (engine.isInboundDone() && !engine.isOutboundDone()) {
|
||||
if (debug.on()) debug.log("doClosure: close_notify received");
|
||||
close_notify_received = true;
|
||||
doHandshake(r, READER);
|
||||
if (!writer.scheduler.isStopped()) {
|
||||
doHandshake(r, READER);
|
||||
} else {
|
||||
// We have received closed notify, but we
|
||||
// won't be able to send the acknowledgement.
|
||||
// Nothing more will come from the socket either,
|
||||
// so mark the reader as completed.
|
||||
synchronized (reader.readBufferLock) {
|
||||
reader.completing = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return r;
|
||||
|
@ -914,12 +997,22 @@ public class SSLFlowDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
public ByteBuffer getNetBuffer() {
|
||||
return ByteBuffer.allocate(engine.getSession().getPacketBufferSize());
|
||||
volatile int packetBufferSize;
|
||||
final ByteBuffer getNetBuffer() {
|
||||
int netSize = packetBufferSize;
|
||||
if (netSize <= 0) {
|
||||
packetBufferSize = netSize = engine.getSession().getPacketBufferSize();
|
||||
}
|
||||
return ByteBuffer.allocate(netSize);
|
||||
}
|
||||
|
||||
private ByteBuffer getAppBuffer() {
|
||||
return ByteBuffer.allocate(engine.getSession().getApplicationBufferSize());
|
||||
volatile int applicationBufferSize;
|
||||
final ByteBuffer getAppBuffer() {
|
||||
int appSize = applicationBufferSize;
|
||||
if (appSize <= 0) {
|
||||
applicationBufferSize = appSize = engine.getSession().getApplicationBufferSize();
|
||||
}
|
||||
return ByteBuffer.allocate(appSize);
|
||||
}
|
||||
|
||||
final String dbgString() {
|
||||
|
|
|
@ -77,6 +77,13 @@ public class SSLTube implements FlowTube {
|
|||
private volatile boolean finished;
|
||||
|
||||
public SSLTube(SSLEngine engine, Executor executor, FlowTube tube) {
|
||||
this(engine, executor, null, tube);
|
||||
}
|
||||
|
||||
public SSLTube(SSLEngine engine,
|
||||
Executor executor,
|
||||
Consumer<ByteBuffer> recycler,
|
||||
FlowTube tube) {
|
||||
Objects.requireNonNull(engine);
|
||||
Objects.requireNonNull(executor);
|
||||
this.tube = Objects.requireNonNull(tube);
|
||||
|
@ -85,15 +92,17 @@ public class SSLTube implements FlowTube {
|
|||
this.engine = engine;
|
||||
sslDelegate = new SSLTubeFlowDelegate(engine,
|
||||
executor,
|
||||
recycler,
|
||||
readSubscriber,
|
||||
tube);
|
||||
}
|
||||
|
||||
final class SSLTubeFlowDelegate extends SSLFlowDelegate {
|
||||
SSLTubeFlowDelegate(SSLEngine engine, Executor executor,
|
||||
Consumer<ByteBuffer> recycler,
|
||||
SSLSubscriberWrapper readSubscriber,
|
||||
FlowTube tube) {
|
||||
super(engine, executor, readSubscriber, tube);
|
||||
super(engine, executor, recycler, readSubscriber, tube);
|
||||
}
|
||||
protected SchedulingAction enterReadScheduling() {
|
||||
readSubscriber.processPendingSubscriber();
|
||||
|
|
|
@ -306,14 +306,16 @@ public abstract class SubscriberWrapper
|
|||
downstreamSubscription);
|
||||
}
|
||||
|
||||
boolean datasent = false;
|
||||
while (!outputQ.isEmpty() && downstreamSubscription.tryDecrement()) {
|
||||
List<ByteBuffer> b = outputQ.poll();
|
||||
if (debug.on())
|
||||
debug.log("DownstreamPusher: Pushing %d bytes downstream",
|
||||
Utils.remaining(b));
|
||||
downstreamSubscriber.onNext(b);
|
||||
datasent = true;
|
||||
}
|
||||
upstreamWindowUpdate();
|
||||
if (datasent) upstreamWindowUpdate();
|
||||
checkCompletion();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,10 +44,14 @@ import java.net.URI;
|
|||
import java.net.URLPermission;
|
||||
import java.net.http.HttpHeaders;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.CharacterCodingException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.CodingErrorAction;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.text.Normalizer;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
@ -581,25 +585,6 @@ public final class Utils {
|
|||
return (int) remain;
|
||||
}
|
||||
|
||||
public static long remaining(ByteBufferReference[] refs) {
|
||||
long remain = 0;
|
||||
for (ByteBufferReference ref : refs) {
|
||||
remain += ref.get().remaining();
|
||||
}
|
||||
return remain;
|
||||
}
|
||||
|
||||
public static int remaining(ByteBufferReference[] refs, int max) {
|
||||
long remain = 0;
|
||||
for (ByteBufferReference ref : refs) {
|
||||
remain += ref.get().remaining();
|
||||
if (remain > max) {
|
||||
throw new IllegalArgumentException("too many bytes");
|
||||
}
|
||||
}
|
||||
return (int) remain;
|
||||
}
|
||||
|
||||
public static int remaining(ByteBuffer[] refs, int max) {
|
||||
long remain = 0;
|
||||
for (ByteBuffer b : refs) {
|
||||
|
@ -623,7 +608,6 @@ public final class Utils {
|
|||
public static final ByteBuffer EMPTY_BYTEBUFFER = ByteBuffer.allocate(0);
|
||||
public static final ByteBuffer[] EMPTY_BB_ARRAY = new ByteBuffer[0];
|
||||
public static final List<ByteBuffer> EMPTY_BB_LIST = List.of();
|
||||
public static final ByteBufferReference[] EMPTY_BBR_ARRAY = new ByteBufferReference[0];
|
||||
|
||||
/**
|
||||
* Returns a slice of size {@code amount} from the given buffer. If the
|
||||
|
@ -959,4 +943,55 @@ public final class Utils {
|
|||
return 1 << (32 - Integer.numberOfLeadingZeros(n - 1));
|
||||
}
|
||||
}
|
||||
|
||||
// -- toAsciiString-like support to encode path and query URI segments
|
||||
|
||||
private static final char[] hexDigits = {
|
||||
'0', '1', '2', '3', '4', '5', '6', '7',
|
||||
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
|
||||
};
|
||||
|
||||
private static void appendEscape(StringBuilder sb, byte b) {
|
||||
sb.append('%');
|
||||
sb.append(hexDigits[(b >> 4) & 0x0f]);
|
||||
sb.append(hexDigits[(b >> 0) & 0x0f]);
|
||||
}
|
||||
|
||||
// Encodes all characters >= \u0080 into escaped, normalized UTF-8 octets,
|
||||
// assuming that s is otherwise legal
|
||||
//
|
||||
public static String encode(String s) {
|
||||
int n = s.length();
|
||||
if (n == 0)
|
||||
return s;
|
||||
|
||||
// First check whether we actually need to encode
|
||||
for (int i = 0;;) {
|
||||
if (s.charAt(i) >= '\u0080')
|
||||
break;
|
||||
if (++i >= n)
|
||||
return s;
|
||||
}
|
||||
|
||||
String ns = Normalizer.normalize(s, Normalizer.Form.NFC);
|
||||
ByteBuffer bb = null;
|
||||
try {
|
||||
bb = StandardCharsets.UTF_8.newEncoder()
|
||||
.onMalformedInput(CodingErrorAction.REPORT)
|
||||
.onUnmappableCharacter(CodingErrorAction.REPORT)
|
||||
.encode(CharBuffer.wrap(ns));
|
||||
} catch (CharacterCodingException x) {
|
||||
assert false : x;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
while (bb.hasRemaining()) {
|
||||
int b = bb.get() & 0xff;
|
||||
if (b >= 0x80)
|
||||
appendEscape(sb, (byte)b);
|
||||
else
|
||||
sb.append((char)b);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,655 +27,60 @@ package jdk.internal.net.http.hpack;
|
|||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
/**
|
||||
* Huffman coding table.
|
||||
*
|
||||
* <p> Instances of this class are safe for use by multiple threads.
|
||||
* Huffman coding.
|
||||
*
|
||||
* @since 9
|
||||
*/
|
||||
public final class Huffman {
|
||||
|
||||
// TODO: check if reset is done in both reader and writer
|
||||
public interface Reader {
|
||||
|
||||
static final class Reader {
|
||||
|
||||
private Node curr; // position in the trie
|
||||
private int len; // length of the path from the root to 'curr'
|
||||
private int p; // byte probe
|
||||
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
public void read(ByteBuffer source,
|
||||
Appendable destination,
|
||||
boolean isLast) throws IOException {
|
||||
read(source, destination, true, isLast);
|
||||
}
|
||||
|
||||
// Takes 'isLast' rather than returns whether the reading is done or
|
||||
// not, for more informative exceptions.
|
||||
void read(ByteBuffer source,
|
||||
Appendable destination,
|
||||
boolean reportEOS, /* reportEOS is exposed for tests */
|
||||
boolean isLast) throws IOException {
|
||||
Node c = curr;
|
||||
int l = len;
|
||||
/*
|
||||
Since ByteBuffer is itself stateful, its position is
|
||||
remembered here NOT as a part of Reader's state,
|
||||
but to set it back in the case of a failure
|
||||
*/
|
||||
int pos = source.position();
|
||||
boolean isLast) throws IOException;
|
||||
|
||||
while (source.hasRemaining()) {
|
||||
int d = source.get();
|
||||
for (; p != 0; p >>= 1) {
|
||||
c = c.getChild(p & d);
|
||||
l++;
|
||||
if (c.isLeaf()) {
|
||||
if (reportEOS && c.isEOSPath) {
|
||||
throw new IOException("Encountered EOS");
|
||||
}
|
||||
char ch;
|
||||
try {
|
||||
ch = c.getChar();
|
||||
} catch (IllegalStateException e) {
|
||||
source.position(pos); // do we need this?
|
||||
throw new IOException(e);
|
||||
}
|
||||
try {
|
||||
destination.append(ch);
|
||||
} catch (IOException e) {
|
||||
source.position(pos); // do we need this?
|
||||
throw e;
|
||||
}
|
||||
c = INSTANCE.root;
|
||||
l = 0;
|
||||
}
|
||||
curr = c;
|
||||
len = l;
|
||||
}
|
||||
resetProbe();
|
||||
pos++;
|
||||
}
|
||||
if (!isLast) {
|
||||
return; // it's too early to jump to any conclusions, let's wait
|
||||
}
|
||||
if (c.isLeaf()) {
|
||||
return; // it's perfectly ok, no extra padding bits
|
||||
}
|
||||
if (c.isEOSPath && len <= 7) {
|
||||
return; // it's ok, some extra padding bits
|
||||
}
|
||||
if (c.isEOSPath) {
|
||||
throw new IOException(
|
||||
"Padding is too long (len=" + len + ") " +
|
||||
"or unexpected end of data");
|
||||
}
|
||||
throw new IOException(
|
||||
"Not a EOS prefix padding or unexpected end of data");
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
curr = INSTANCE.root;
|
||||
len = 0;
|
||||
resetProbe();
|
||||
}
|
||||
|
||||
private void resetProbe() {
|
||||
p = 0x80;
|
||||
}
|
||||
/**
|
||||
* Brings this reader to the state it had upon construction.
|
||||
*/
|
||||
void reset();
|
||||
}
|
||||
|
||||
static final class Writer {
|
||||
public interface Writer {
|
||||
|
||||
private int pos; // position in 'source'
|
||||
private int avail = 8; // number of least significant bits available in 'curr'
|
||||
private int curr; // next byte to put to the destination
|
||||
private int rem; // number of least significant bits in 'code' yet to be processed
|
||||
private int code; // current code being written
|
||||
Writer from(CharSequence input, int start, int end);
|
||||
|
||||
private CharSequence source;
|
||||
private int end;
|
||||
boolean write(ByteBuffer destination);
|
||||
|
||||
public Writer from(CharSequence input, int start, int end) {
|
||||
if (start < 0 || end < 0 || end > input.length() || start > end) {
|
||||
throw new IndexOutOfBoundsException(
|
||||
String.format("input.length()=%s, start=%s, end=%s",
|
||||
input.length(), start, end));
|
||||
}
|
||||
pos = start;
|
||||
this.end = end;
|
||||
this.source = input;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Brings this writer to the state it had upon construction.
|
||||
*
|
||||
* @return this writer
|
||||
*/
|
||||
Writer reset();
|
||||
|
||||
public boolean write(ByteBuffer destination) {
|
||||
for (; pos < end; pos++) {
|
||||
if (rem == 0) {
|
||||
Code desc = INSTANCE.codeOf(source.charAt(pos));
|
||||
rem = desc.length;
|
||||
code = desc.code;
|
||||
}
|
||||
while (rem > 0) {
|
||||
if (rem < avail) {
|
||||
curr |= (code << (avail - rem));
|
||||
avail -= rem;
|
||||
rem = 0;
|
||||
} else {
|
||||
int c = (curr | (code >>> (rem - avail)));
|
||||
if (destination.hasRemaining()) {
|
||||
destination.put((byte) c);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
curr = c;
|
||||
code <<= (32 - rem + avail); // throw written bits off the cliff (is this Sparta?)
|
||||
code >>>= (32 - rem + avail); // return to the position
|
||||
rem -= avail;
|
||||
curr = 0;
|
||||
avail = 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Calculates the number of bytes required to represent a subsequence of
|
||||
* the given {@code CharSequence} using the Huffman coding.
|
||||
*
|
||||
* @param value
|
||||
* characters
|
||||
* @param start
|
||||
* the start index, inclusive
|
||||
* @param end
|
||||
* the end index, exclusive
|
||||
*
|
||||
* @return number of bytes
|
||||
* @throws NullPointerException
|
||||
* if the value is null
|
||||
* @throws IndexOutOfBoundsException
|
||||
* if any invocation of {@code value.charAt(i)}, where
|
||||
* {@code start <= i < end}, throws an IndexOutOfBoundsException
|
||||
*/
|
||||
int lengthOf(CharSequence value, int start, int end);
|
||||
|
||||
if (avail < 8) { // have to pad
|
||||
if (destination.hasRemaining()) {
|
||||
destination.put((byte) (curr | (INSTANCE.EOS.code >>> (INSTANCE.EOS.length - avail))));
|
||||
avail = 8;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public Writer reset() {
|
||||
source = null;
|
||||
end = -1;
|
||||
pos = -1;
|
||||
avail = 8;
|
||||
curr = 0;
|
||||
code = 0;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared instance.
|
||||
*/
|
||||
public static final Huffman INSTANCE = new Huffman();
|
||||
|
||||
private final Code EOS = new Code(0x3fffffff, 30);
|
||||
private final Code[] codes = new Code[257];
|
||||
private final Node root = new Node() {
|
||||
@Override
|
||||
public String toString() { return "root"; }
|
||||
};
|
||||
|
||||
// TODO: consider builder and immutable trie
|
||||
private Huffman() {
|
||||
// @formatter:off
|
||||
addChar(0, 0x1ff8, 13);
|
||||
addChar(1, 0x7fffd8, 23);
|
||||
addChar(2, 0xfffffe2, 28);
|
||||
addChar(3, 0xfffffe3, 28);
|
||||
addChar(4, 0xfffffe4, 28);
|
||||
addChar(5, 0xfffffe5, 28);
|
||||
addChar(6, 0xfffffe6, 28);
|
||||
addChar(7, 0xfffffe7, 28);
|
||||
addChar(8, 0xfffffe8, 28);
|
||||
addChar(9, 0xffffea, 24);
|
||||
addChar(10, 0x3ffffffc, 30);
|
||||
addChar(11, 0xfffffe9, 28);
|
||||
addChar(12, 0xfffffea, 28);
|
||||
addChar(13, 0x3ffffffd, 30);
|
||||
addChar(14, 0xfffffeb, 28);
|
||||
addChar(15, 0xfffffec, 28);
|
||||
addChar(16, 0xfffffed, 28);
|
||||
addChar(17, 0xfffffee, 28);
|
||||
addChar(18, 0xfffffef, 28);
|
||||
addChar(19, 0xffffff0, 28);
|
||||
addChar(20, 0xffffff1, 28);
|
||||
addChar(21, 0xffffff2, 28);
|
||||
addChar(22, 0x3ffffffe, 30);
|
||||
addChar(23, 0xffffff3, 28);
|
||||
addChar(24, 0xffffff4, 28);
|
||||
addChar(25, 0xffffff5, 28);
|
||||
addChar(26, 0xffffff6, 28);
|
||||
addChar(27, 0xffffff7, 28);
|
||||
addChar(28, 0xffffff8, 28);
|
||||
addChar(29, 0xffffff9, 28);
|
||||
addChar(30, 0xffffffa, 28);
|
||||
addChar(31, 0xffffffb, 28);
|
||||
addChar(32, 0x14, 6);
|
||||
addChar(33, 0x3f8, 10);
|
||||
addChar(34, 0x3f9, 10);
|
||||
addChar(35, 0xffa, 12);
|
||||
addChar(36, 0x1ff9, 13);
|
||||
addChar(37, 0x15, 6);
|
||||
addChar(38, 0xf8, 8);
|
||||
addChar(39, 0x7fa, 11);
|
||||
addChar(40, 0x3fa, 10);
|
||||
addChar(41, 0x3fb, 10);
|
||||
addChar(42, 0xf9, 8);
|
||||
addChar(43, 0x7fb, 11);
|
||||
addChar(44, 0xfa, 8);
|
||||
addChar(45, 0x16, 6);
|
||||
addChar(46, 0x17, 6);
|
||||
addChar(47, 0x18, 6);
|
||||
addChar(48, 0x0, 5);
|
||||
addChar(49, 0x1, 5);
|
||||
addChar(50, 0x2, 5);
|
||||
addChar(51, 0x19, 6);
|
||||
addChar(52, 0x1a, 6);
|
||||
addChar(53, 0x1b, 6);
|
||||
addChar(54, 0x1c, 6);
|
||||
addChar(55, 0x1d, 6);
|
||||
addChar(56, 0x1e, 6);
|
||||
addChar(57, 0x1f, 6);
|
||||
addChar(58, 0x5c, 7);
|
||||
addChar(59, 0xfb, 8);
|
||||
addChar(60, 0x7ffc, 15);
|
||||
addChar(61, 0x20, 6);
|
||||
addChar(62, 0xffb, 12);
|
||||
addChar(63, 0x3fc, 10);
|
||||
addChar(64, 0x1ffa, 13);
|
||||
addChar(65, 0x21, 6);
|
||||
addChar(66, 0x5d, 7);
|
||||
addChar(67, 0x5e, 7);
|
||||
addChar(68, 0x5f, 7);
|
||||
addChar(69, 0x60, 7);
|
||||
addChar(70, 0x61, 7);
|
||||
addChar(71, 0x62, 7);
|
||||
addChar(72, 0x63, 7);
|
||||
addChar(73, 0x64, 7);
|
||||
addChar(74, 0x65, 7);
|
||||
addChar(75, 0x66, 7);
|
||||
addChar(76, 0x67, 7);
|
||||
addChar(77, 0x68, 7);
|
||||
addChar(78, 0x69, 7);
|
||||
addChar(79, 0x6a, 7);
|
||||
addChar(80, 0x6b, 7);
|
||||
addChar(81, 0x6c, 7);
|
||||
addChar(82, 0x6d, 7);
|
||||
addChar(83, 0x6e, 7);
|
||||
addChar(84, 0x6f, 7);
|
||||
addChar(85, 0x70, 7);
|
||||
addChar(86, 0x71, 7);
|
||||
addChar(87, 0x72, 7);
|
||||
addChar(88, 0xfc, 8);
|
||||
addChar(89, 0x73, 7);
|
||||
addChar(90, 0xfd, 8);
|
||||
addChar(91, 0x1ffb, 13);
|
||||
addChar(92, 0x7fff0, 19);
|
||||
addChar(93, 0x1ffc, 13);
|
||||
addChar(94, 0x3ffc, 14);
|
||||
addChar(95, 0x22, 6);
|
||||
addChar(96, 0x7ffd, 15);
|
||||
addChar(97, 0x3, 5);
|
||||
addChar(98, 0x23, 6);
|
||||
addChar(99, 0x4, 5);
|
||||
addChar(100, 0x24, 6);
|
||||
addChar(101, 0x5, 5);
|
||||
addChar(102, 0x25, 6);
|
||||
addChar(103, 0x26, 6);
|
||||
addChar(104, 0x27, 6);
|
||||
addChar(105, 0x6, 5);
|
||||
addChar(106, 0x74, 7);
|
||||
addChar(107, 0x75, 7);
|
||||
addChar(108, 0x28, 6);
|
||||
addChar(109, 0x29, 6);
|
||||
addChar(110, 0x2a, 6);
|
||||
addChar(111, 0x7, 5);
|
||||
addChar(112, 0x2b, 6);
|
||||
addChar(113, 0x76, 7);
|
||||
addChar(114, 0x2c, 6);
|
||||
addChar(115, 0x8, 5);
|
||||
addChar(116, 0x9, 5);
|
||||
addChar(117, 0x2d, 6);
|
||||
addChar(118, 0x77, 7);
|
||||
addChar(119, 0x78, 7);
|
||||
addChar(120, 0x79, 7);
|
||||
addChar(121, 0x7a, 7);
|
||||
addChar(122, 0x7b, 7);
|
||||
addChar(123, 0x7ffe, 15);
|
||||
addChar(124, 0x7fc, 11);
|
||||
addChar(125, 0x3ffd, 14);
|
||||
addChar(126, 0x1ffd, 13);
|
||||
addChar(127, 0xffffffc, 28);
|
||||
addChar(128, 0xfffe6, 20);
|
||||
addChar(129, 0x3fffd2, 22);
|
||||
addChar(130, 0xfffe7, 20);
|
||||
addChar(131, 0xfffe8, 20);
|
||||
addChar(132, 0x3fffd3, 22);
|
||||
addChar(133, 0x3fffd4, 22);
|
||||
addChar(134, 0x3fffd5, 22);
|
||||
addChar(135, 0x7fffd9, 23);
|
||||
addChar(136, 0x3fffd6, 22);
|
||||
addChar(137, 0x7fffda, 23);
|
||||
addChar(138, 0x7fffdb, 23);
|
||||
addChar(139, 0x7fffdc, 23);
|
||||
addChar(140, 0x7fffdd, 23);
|
||||
addChar(141, 0x7fffde, 23);
|
||||
addChar(142, 0xffffeb, 24);
|
||||
addChar(143, 0x7fffdf, 23);
|
||||
addChar(144, 0xffffec, 24);
|
||||
addChar(145, 0xffffed, 24);
|
||||
addChar(146, 0x3fffd7, 22);
|
||||
addChar(147, 0x7fffe0, 23);
|
||||
addChar(148, 0xffffee, 24);
|
||||
addChar(149, 0x7fffe1, 23);
|
||||
addChar(150, 0x7fffe2, 23);
|
||||
addChar(151, 0x7fffe3, 23);
|
||||
addChar(152, 0x7fffe4, 23);
|
||||
addChar(153, 0x1fffdc, 21);
|
||||
addChar(154, 0x3fffd8, 22);
|
||||
addChar(155, 0x7fffe5, 23);
|
||||
addChar(156, 0x3fffd9, 22);
|
||||
addChar(157, 0x7fffe6, 23);
|
||||
addChar(158, 0x7fffe7, 23);
|
||||
addChar(159, 0xffffef, 24);
|
||||
addChar(160, 0x3fffda, 22);
|
||||
addChar(161, 0x1fffdd, 21);
|
||||
addChar(162, 0xfffe9, 20);
|
||||
addChar(163, 0x3fffdb, 22);
|
||||
addChar(164, 0x3fffdc, 22);
|
||||
addChar(165, 0x7fffe8, 23);
|
||||
addChar(166, 0x7fffe9, 23);
|
||||
addChar(167, 0x1fffde, 21);
|
||||
addChar(168, 0x7fffea, 23);
|
||||
addChar(169, 0x3fffdd, 22);
|
||||
addChar(170, 0x3fffde, 22);
|
||||
addChar(171, 0xfffff0, 24);
|
||||
addChar(172, 0x1fffdf, 21);
|
||||
addChar(173, 0x3fffdf, 22);
|
||||
addChar(174, 0x7fffeb, 23);
|
||||
addChar(175, 0x7fffec, 23);
|
||||
addChar(176, 0x1fffe0, 21);
|
||||
addChar(177, 0x1fffe1, 21);
|
||||
addChar(178, 0x3fffe0, 22);
|
||||
addChar(179, 0x1fffe2, 21);
|
||||
addChar(180, 0x7fffed, 23);
|
||||
addChar(181, 0x3fffe1, 22);
|
||||
addChar(182, 0x7fffee, 23);
|
||||
addChar(183, 0x7fffef, 23);
|
||||
addChar(184, 0xfffea, 20);
|
||||
addChar(185, 0x3fffe2, 22);
|
||||
addChar(186, 0x3fffe3, 22);
|
||||
addChar(187, 0x3fffe4, 22);
|
||||
addChar(188, 0x7ffff0, 23);
|
||||
addChar(189, 0x3fffe5, 22);
|
||||
addChar(190, 0x3fffe6, 22);
|
||||
addChar(191, 0x7ffff1, 23);
|
||||
addChar(192, 0x3ffffe0, 26);
|
||||
addChar(193, 0x3ffffe1, 26);
|
||||
addChar(194, 0xfffeb, 20);
|
||||
addChar(195, 0x7fff1, 19);
|
||||
addChar(196, 0x3fffe7, 22);
|
||||
addChar(197, 0x7ffff2, 23);
|
||||
addChar(198, 0x3fffe8, 22);
|
||||
addChar(199, 0x1ffffec, 25);
|
||||
addChar(200, 0x3ffffe2, 26);
|
||||
addChar(201, 0x3ffffe3, 26);
|
||||
addChar(202, 0x3ffffe4, 26);
|
||||
addChar(203, 0x7ffffde, 27);
|
||||
addChar(204, 0x7ffffdf, 27);
|
||||
addChar(205, 0x3ffffe5, 26);
|
||||
addChar(206, 0xfffff1, 24);
|
||||
addChar(207, 0x1ffffed, 25);
|
||||
addChar(208, 0x7fff2, 19);
|
||||
addChar(209, 0x1fffe3, 21);
|
||||
addChar(210, 0x3ffffe6, 26);
|
||||
addChar(211, 0x7ffffe0, 27);
|
||||
addChar(212, 0x7ffffe1, 27);
|
||||
addChar(213, 0x3ffffe7, 26);
|
||||
addChar(214, 0x7ffffe2, 27);
|
||||
addChar(215, 0xfffff2, 24);
|
||||
addChar(216, 0x1fffe4, 21);
|
||||
addChar(217, 0x1fffe5, 21);
|
||||
addChar(218, 0x3ffffe8, 26);
|
||||
addChar(219, 0x3ffffe9, 26);
|
||||
addChar(220, 0xffffffd, 28);
|
||||
addChar(221, 0x7ffffe3, 27);
|
||||
addChar(222, 0x7ffffe4, 27);
|
||||
addChar(223, 0x7ffffe5, 27);
|
||||
addChar(224, 0xfffec, 20);
|
||||
addChar(225, 0xfffff3, 24);
|
||||
addChar(226, 0xfffed, 20);
|
||||
addChar(227, 0x1fffe6, 21);
|
||||
addChar(228, 0x3fffe9, 22);
|
||||
addChar(229, 0x1fffe7, 21);
|
||||
addChar(230, 0x1fffe8, 21);
|
||||
addChar(231, 0x7ffff3, 23);
|
||||
addChar(232, 0x3fffea, 22);
|
||||
addChar(233, 0x3fffeb, 22);
|
||||
addChar(234, 0x1ffffee, 25);
|
||||
addChar(235, 0x1ffffef, 25);
|
||||
addChar(236, 0xfffff4, 24);
|
||||
addChar(237, 0xfffff5, 24);
|
||||
addChar(238, 0x3ffffea, 26);
|
||||
addChar(239, 0x7ffff4, 23);
|
||||
addChar(240, 0x3ffffeb, 26);
|
||||
addChar(241, 0x7ffffe6, 27);
|
||||
addChar(242, 0x3ffffec, 26);
|
||||
addChar(243, 0x3ffffed, 26);
|
||||
addChar(244, 0x7ffffe7, 27);
|
||||
addChar(245, 0x7ffffe8, 27);
|
||||
addChar(246, 0x7ffffe9, 27);
|
||||
addChar(247, 0x7ffffea, 27);
|
||||
addChar(248, 0x7ffffeb, 27);
|
||||
addChar(249, 0xffffffe, 28);
|
||||
addChar(250, 0x7ffffec, 27);
|
||||
addChar(251, 0x7ffffed, 27);
|
||||
addChar(252, 0x7ffffee, 27);
|
||||
addChar(253, 0x7ffffef, 27);
|
||||
addChar(254, 0x7fffff0, 27);
|
||||
addChar(255, 0x3ffffee, 26);
|
||||
addEOS (256, EOS.code, EOS.length);
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculates the number of bytes required to represent the given {@code
|
||||
* CharSequence} with the Huffman coding.
|
||||
*
|
||||
* @param value
|
||||
* characters
|
||||
*
|
||||
* @return number of bytes
|
||||
*
|
||||
* @throws NullPointerException
|
||||
* if the value is null
|
||||
*/
|
||||
public int lengthOf(CharSequence value) {
|
||||
return lengthOf(value, 0, value.length());
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the number of bytes required to represent a subsequence of the
|
||||
* given {@code CharSequence} with the Huffman coding.
|
||||
*
|
||||
* @param value
|
||||
* characters
|
||||
* @param start
|
||||
* the start index, inclusive
|
||||
* @param end
|
||||
* the end index, exclusive
|
||||
*
|
||||
* @return number of bytes
|
||||
*
|
||||
* @throws NullPointerException
|
||||
* if the value is null
|
||||
* @throws IndexOutOfBoundsException
|
||||
* if any invocation of {@code value.charAt(i)}, where
|
||||
* {@code start <= i < end} would throw an IndexOutOfBoundsException
|
||||
*/
|
||||
public int lengthOf(CharSequence value, int start, int end) {
|
||||
int len = 0;
|
||||
for (int i = start; i < end; i++) {
|
||||
char c = value.charAt(i);
|
||||
len += INSTANCE.codeOf(c).length;
|
||||
}
|
||||
// Integer division with ceiling, assumption:
|
||||
assert (len / 8 + (len % 8 != 0 ? 1 : 0)) == (len + 7) / 8 : len;
|
||||
return (len + 7) / 8;
|
||||
}
|
||||
|
||||
private void addChar(int c, int code, int bitLength) {
|
||||
addLeaf(c, code, bitLength, false);
|
||||
codes[c] = new Code(code, bitLength);
|
||||
}
|
||||
|
||||
private void addEOS(int c, int code, int bitLength) {
|
||||
addLeaf(c, code, bitLength, true);
|
||||
codes[c] = new Code(code, bitLength);
|
||||
}
|
||||
|
||||
private void addLeaf(int c, int code, int bitLength, boolean isEOS) {
|
||||
if (bitLength < 1) {
|
||||
throw new IllegalArgumentException("bitLength < 1");
|
||||
}
|
||||
Node curr = root;
|
||||
for (int p = 1 << bitLength - 1; p != 0 && !curr.isLeaf(); p = p >> 1) {
|
||||
curr.isEOSPath |= isEOS; // If it's already true, it can't become false
|
||||
curr = curr.addChildIfAbsent(p & code);
|
||||
}
|
||||
curr.isEOSPath |= isEOS; // The last one needs to have this property as well
|
||||
if (curr.isLeaf()) {
|
||||
throw new IllegalStateException("Specified code is already taken");
|
||||
}
|
||||
curr.setChar((char) c);
|
||||
}
|
||||
|
||||
private Code codeOf(char c) {
|
||||
if (c > 255) {
|
||||
throw new IllegalArgumentException("char=" + ((int) c));
|
||||
}
|
||||
return codes[c];
|
||||
}
|
||||
|
||||
//
|
||||
// For debugging/testing purposes
|
||||
//
|
||||
Node getRoot() {
|
||||
return root;
|
||||
}
|
||||
|
||||
//
|
||||
// Guarantees:
|
||||
//
|
||||
// if (isLeaf() == true) => getChar() is a legal call
|
||||
// if (isLeaf() == false) => getChild(i) is a legal call (though it can
|
||||
// return null)
|
||||
//
|
||||
static class Node {
|
||||
|
||||
Node left;
|
||||
Node right;
|
||||
boolean isEOSPath;
|
||||
|
||||
boolean charIsSet;
|
||||
char c;
|
||||
|
||||
Node getChild(int selector) {
|
||||
if (isLeaf()) {
|
||||
throw new IllegalStateException("This is a leaf node");
|
||||
}
|
||||
Node result = selector == 0 ? left : right;
|
||||
if (result == null) {
|
||||
throw new IllegalStateException(format(
|
||||
"Node doesn't have a child (selector=%s)", selector));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
boolean isLeaf() {
|
||||
return charIsSet;
|
||||
}
|
||||
|
||||
char getChar() {
|
||||
if (!isLeaf()) {
|
||||
throw new IllegalStateException("This node is not a leaf node");
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
void setChar(char c) {
|
||||
if (charIsSet) {
|
||||
throw new IllegalStateException(
|
||||
"This node has been taken already");
|
||||
}
|
||||
if (left != null || right != null) {
|
||||
throw new IllegalStateException("The node cannot be made "
|
||||
+ "a leaf as it's already has a child");
|
||||
}
|
||||
this.c = c;
|
||||
charIsSet = true;
|
||||
}
|
||||
|
||||
Node addChildIfAbsent(int i) {
|
||||
if (charIsSet) {
|
||||
throw new IllegalStateException("The node cannot have a child "
|
||||
+ "as it's already a leaf node");
|
||||
}
|
||||
Node child;
|
||||
if (i == 0) {
|
||||
if ((child = left) == null) {
|
||||
child = left = new Node();
|
||||
}
|
||||
} else {
|
||||
if ((child = right) == null) {
|
||||
child = right = new Node();
|
||||
}
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (isLeaf()) {
|
||||
if (isEOSPath) {
|
||||
return "EOS";
|
||||
} else {
|
||||
return format("char: (%3s) '%s'", (int) c, c);
|
||||
}
|
||||
}
|
||||
return "/\\";
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: value-based class?
|
||||
// FIXME: can we re-use Node instead of this class?
|
||||
private static final class Code {
|
||||
|
||||
final int code;
|
||||
final int length;
|
||||
|
||||
private Code(int code, int length) {
|
||||
this.code = code;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public int getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
long p = 1 << length;
|
||||
return Long.toBinaryString(code + p).substring(1)
|
||||
+ ", length=" + length;
|
||||
default int lengthOf(CharSequence value) {
|
||||
return lengthOf(value, 0, value.length());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,691 @@
|
|||
/*
|
||||
* Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.internal.net.http.hpack;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
/**
|
||||
* Huffman coding table.
|
||||
*
|
||||
* <p> Instances of this class are safe for use by multiple threads.
|
||||
*
|
||||
* @since 9
|
||||
*/
|
||||
public final class NaiveHuffman {
|
||||
|
||||
// TODO: check if reset is done in both reader and writer
|
||||
|
||||
static final class Reader implements Huffman.Reader {
|
||||
|
||||
private Node curr; // position in the trie
|
||||
private int len; // length of the path from the root to 'curr'
|
||||
private int p; // byte probe
|
||||
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(ByteBuffer source,
|
||||
Appendable destination,
|
||||
boolean isLast) throws IOException {
|
||||
read(source, destination, true, isLast);
|
||||
}
|
||||
|
||||
// Takes 'isLast' rather than returns whether the reading is done or
|
||||
// not, for more informative exceptions.
|
||||
void read(ByteBuffer source,
|
||||
Appendable destination,
|
||||
boolean reportEOS, /* reportEOS is exposed for tests */
|
||||
boolean isLast) throws IOException {
|
||||
Node c = curr;
|
||||
int l = len;
|
||||
/*
|
||||
Since ByteBuffer is itself stateful, its position is
|
||||
remembered here NOT as a part of Reader's state,
|
||||
but to set it back in the case of a failure
|
||||
*/
|
||||
int pos = source.position();
|
||||
|
||||
while (source.hasRemaining()) {
|
||||
int d = source.get();
|
||||
for (; p != 0; p >>= 1) {
|
||||
c = c.getChild(p & d);
|
||||
l++;
|
||||
if (c.isLeaf()) {
|
||||
if (reportEOS && c.isEOSPath) {
|
||||
throw new IOException("Encountered EOS");
|
||||
}
|
||||
char ch;
|
||||
try {
|
||||
ch = c.getChar();
|
||||
} catch (IllegalStateException e) {
|
||||
source.position(pos); // do we need this?
|
||||
throw new IOException(e);
|
||||
}
|
||||
try {
|
||||
destination.append(ch);
|
||||
} catch (IOException e) {
|
||||
source.position(pos); // do we need this?
|
||||
throw e;
|
||||
}
|
||||
c = INSTANCE.root;
|
||||
l = 0;
|
||||
}
|
||||
curr = c;
|
||||
len = l;
|
||||
}
|
||||
resetProbe();
|
||||
pos++;
|
||||
}
|
||||
if (!isLast) {
|
||||
return; // it's too early to jump to any conclusions, let's wait
|
||||
}
|
||||
if (c.isLeaf()) {
|
||||
return; // it's perfectly ok, no extra padding bits
|
||||
}
|
||||
if (c.isEOSPath && len <= 7) {
|
||||
return; // it's ok, some extra padding bits
|
||||
}
|
||||
if (c.isEOSPath) {
|
||||
throw new IOException(
|
||||
"Padding is too long (len=" + len + ") " +
|
||||
"or unexpected end of data");
|
||||
}
|
||||
throw new IOException(
|
||||
"Not a EOS prefix padding or unexpected end of data");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
curr = INSTANCE.root;
|
||||
len = 0;
|
||||
resetProbe();
|
||||
}
|
||||
|
||||
private void resetProbe() {
|
||||
p = 0x80;
|
||||
}
|
||||
}
|
||||
|
||||
static final class Writer implements Huffman.Writer {
|
||||
|
||||
private int pos; // position in 'source'
|
||||
private int avail = 8; // number of least significant bits available in 'curr'
|
||||
private int curr; // next byte to put to the destination
|
||||
private int rem; // number of least significant bits in 'code' yet to be processed
|
||||
private int code; // current code being written
|
||||
|
||||
private CharSequence source;
|
||||
private int end;
|
||||
|
||||
@Override
|
||||
public Writer from(CharSequence input, int start, int end) {
|
||||
if (start < 0 || end < 0 || end > input.length() || start > end) {
|
||||
throw new IndexOutOfBoundsException(
|
||||
String.format("input.length()=%s, start=%s, end=%s",
|
||||
input.length(), start, end));
|
||||
}
|
||||
pos = start;
|
||||
this.end = end;
|
||||
this.source = input;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean write(ByteBuffer destination) {
|
||||
for (; pos < end; pos++) {
|
||||
if (rem == 0) {
|
||||
Code desc = INSTANCE.codeOf(source.charAt(pos));
|
||||
rem = desc.length;
|
||||
code = desc.code;
|
||||
}
|
||||
while (rem > 0) {
|
||||
if (rem < avail) {
|
||||
curr |= (code << (avail - rem));
|
||||
avail -= rem;
|
||||
rem = 0;
|
||||
} else {
|
||||
int c = (curr | (code >>> (rem - avail)));
|
||||
if (destination.hasRemaining()) {
|
||||
destination.put((byte) c);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
curr = c;
|
||||
code <<= (32 - rem + avail); // throw written bits off the cliff (is this Sparta?)
|
||||
code >>>= (32 - rem + avail); // return to the position
|
||||
rem -= avail;
|
||||
curr = 0;
|
||||
avail = 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (avail < 8) { // have to pad
|
||||
if (destination.hasRemaining()) {
|
||||
destination.put((byte) (curr | (INSTANCE.EOS.code >>> (INSTANCE.EOS.length - avail))));
|
||||
avail = 8;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Writer reset() {
|
||||
source = null;
|
||||
end = -1;
|
||||
pos = -1;
|
||||
avail = 8;
|
||||
curr = 0;
|
||||
code = 0;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int lengthOf(CharSequence value, int start, int end) {
|
||||
return INSTANCE.lengthOf(value, start, end);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared instance.
|
||||
*/
|
||||
public static final NaiveHuffman INSTANCE = new NaiveHuffman();
|
||||
|
||||
private final Code EOS = new Code(0x3fffffff, 30);
|
||||
private final Code[] codes = new Code[257];
|
||||
private final Node root = new Node() {
|
||||
@Override
|
||||
public String toString() { return "root"; }
|
||||
};
|
||||
|
||||
// TODO: consider builder and immutable trie
|
||||
private NaiveHuffman() {
|
||||
// @formatter:off
|
||||
addChar(0, 0x1ff8, 13);
|
||||
addChar(1, 0x7fffd8, 23);
|
||||
addChar(2, 0xfffffe2, 28);
|
||||
addChar(3, 0xfffffe3, 28);
|
||||
addChar(4, 0xfffffe4, 28);
|
||||
addChar(5, 0xfffffe5, 28);
|
||||
addChar(6, 0xfffffe6, 28);
|
||||
addChar(7, 0xfffffe7, 28);
|
||||
addChar(8, 0xfffffe8, 28);
|
||||
addChar(9, 0xffffea, 24);
|
||||
addChar(10, 0x3ffffffc, 30);
|
||||
addChar(11, 0xfffffe9, 28);
|
||||
addChar(12, 0xfffffea, 28);
|
||||
addChar(13, 0x3ffffffd, 30);
|
||||
addChar(14, 0xfffffeb, 28);
|
||||
addChar(15, 0xfffffec, 28);
|
||||
addChar(16, 0xfffffed, 28);
|
||||
addChar(17, 0xfffffee, 28);
|
||||
addChar(18, 0xfffffef, 28);
|
||||
addChar(19, 0xffffff0, 28);
|
||||
addChar(20, 0xffffff1, 28);
|
||||
addChar(21, 0xffffff2, 28);
|
||||
addChar(22, 0x3ffffffe, 30);
|
||||
addChar(23, 0xffffff3, 28);
|
||||
addChar(24, 0xffffff4, 28);
|
||||
addChar(25, 0xffffff5, 28);
|
||||
addChar(26, 0xffffff6, 28);
|
||||
addChar(27, 0xffffff7, 28);
|
||||
addChar(28, 0xffffff8, 28);
|
||||
addChar(29, 0xffffff9, 28);
|
||||
addChar(30, 0xffffffa, 28);
|
||||
addChar(31, 0xffffffb, 28);
|
||||
addChar(32, 0x14, 6);
|
||||
addChar(33, 0x3f8, 10);
|
||||
addChar(34, 0x3f9, 10);
|
||||
addChar(35, 0xffa, 12);
|
||||
addChar(36, 0x1ff9, 13);
|
||||
addChar(37, 0x15, 6);
|
||||
addChar(38, 0xf8, 8);
|
||||
addChar(39, 0x7fa, 11);
|
||||
addChar(40, 0x3fa, 10);
|
||||
addChar(41, 0x3fb, 10);
|
||||
addChar(42, 0xf9, 8);
|
||||
addChar(43, 0x7fb, 11);
|
||||
addChar(44, 0xfa, 8);
|
||||
addChar(45, 0x16, 6);
|
||||
addChar(46, 0x17, 6);
|
||||
addChar(47, 0x18, 6);
|
||||
addChar(48, 0x0, 5);
|
||||
addChar(49, 0x1, 5);
|
||||
addChar(50, 0x2, 5);
|
||||
addChar(51, 0x19, 6);
|
||||
addChar(52, 0x1a, 6);
|
||||
addChar(53, 0x1b, 6);
|
||||
addChar(54, 0x1c, 6);
|
||||
addChar(55, 0x1d, 6);
|
||||
addChar(56, 0x1e, 6);
|
||||
addChar(57, 0x1f, 6);
|
||||
addChar(58, 0x5c, 7);
|
||||
addChar(59, 0xfb, 8);
|
||||
addChar(60, 0x7ffc, 15);
|
||||
addChar(61, 0x20, 6);
|
||||
addChar(62, 0xffb, 12);
|
||||
addChar(63, 0x3fc, 10);
|
||||
addChar(64, 0x1ffa, 13);
|
||||
addChar(65, 0x21, 6);
|
||||
addChar(66, 0x5d, 7);
|
||||
addChar(67, 0x5e, 7);
|
||||
addChar(68, 0x5f, 7);
|
||||
addChar(69, 0x60, 7);
|
||||
addChar(70, 0x61, 7);
|
||||
addChar(71, 0x62, 7);
|
||||
addChar(72, 0x63, 7);
|
||||
addChar(73, 0x64, 7);
|
||||
addChar(74, 0x65, 7);
|
||||
addChar(75, 0x66, 7);
|
||||
addChar(76, 0x67, 7);
|
||||
addChar(77, 0x68, 7);
|
||||
addChar(78, 0x69, 7);
|
||||
addChar(79, 0x6a, 7);
|
||||
addChar(80, 0x6b, 7);
|
||||
addChar(81, 0x6c, 7);
|
||||
addChar(82, 0x6d, 7);
|
||||
addChar(83, 0x6e, 7);
|
||||
addChar(84, 0x6f, 7);
|
||||
addChar(85, 0x70, 7);
|
||||
addChar(86, 0x71, 7);
|
||||
addChar(87, 0x72, 7);
|
||||
addChar(88, 0xfc, 8);
|
||||
addChar(89, 0x73, 7);
|
||||
addChar(90, 0xfd, 8);
|
||||
addChar(91, 0x1ffb, 13);
|
||||
addChar(92, 0x7fff0, 19);
|
||||
addChar(93, 0x1ffc, 13);
|
||||
addChar(94, 0x3ffc, 14);
|
||||
addChar(95, 0x22, 6);
|
||||
addChar(96, 0x7ffd, 15);
|
||||
addChar(97, 0x3, 5);
|
||||
addChar(98, 0x23, 6);
|
||||
addChar(99, 0x4, 5);
|
||||
addChar(100, 0x24, 6);
|
||||
addChar(101, 0x5, 5);
|
||||
addChar(102, 0x25, 6);
|
||||
addChar(103, 0x26, 6);
|
||||
addChar(104, 0x27, 6);
|
||||
addChar(105, 0x6, 5);
|
||||
addChar(106, 0x74, 7);
|
||||
addChar(107, 0x75, 7);
|
||||
addChar(108, 0x28, 6);
|
||||
addChar(109, 0x29, 6);
|
||||
addChar(110, 0x2a, 6);
|
||||
addChar(111, 0x7, 5);
|
||||
addChar(112, 0x2b, 6);
|
||||
addChar(113, 0x76, 7);
|
||||
addChar(114, 0x2c, 6);
|
||||
addChar(115, 0x8, 5);
|
||||
addChar(116, 0x9, 5);
|
||||
addChar(117, 0x2d, 6);
|
||||
addChar(118, 0x77, 7);
|
||||
addChar(119, 0x78, 7);
|
||||
addChar(120, 0x79, 7);
|
||||
addChar(121, 0x7a, 7);
|
||||
addChar(122, 0x7b, 7);
|
||||
addChar(123, 0x7ffe, 15);
|
||||
addChar(124, 0x7fc, 11);
|
||||
addChar(125, 0x3ffd, 14);
|
||||
addChar(126, 0x1ffd, 13);
|
||||
addChar(127, 0xffffffc, 28);
|
||||
addChar(128, 0xfffe6, 20);
|
||||
addChar(129, 0x3fffd2, 22);
|
||||
addChar(130, 0xfffe7, 20);
|
||||
addChar(131, 0xfffe8, 20);
|
||||
addChar(132, 0x3fffd3, 22);
|
||||
addChar(133, 0x3fffd4, 22);
|
||||
addChar(134, 0x3fffd5, 22);
|
||||
addChar(135, 0x7fffd9, 23);
|
||||
addChar(136, 0x3fffd6, 22);
|
||||
addChar(137, 0x7fffda, 23);
|
||||
addChar(138, 0x7fffdb, 23);
|
||||
addChar(139, 0x7fffdc, 23);
|
||||
addChar(140, 0x7fffdd, 23);
|
||||
addChar(141, 0x7fffde, 23);
|
||||
addChar(142, 0xffffeb, 24);
|
||||
addChar(143, 0x7fffdf, 23);
|
||||
addChar(144, 0xffffec, 24);
|
||||
addChar(145, 0xffffed, 24);
|
||||
addChar(146, 0x3fffd7, 22);
|
||||
addChar(147, 0x7fffe0, 23);
|
||||
addChar(148, 0xffffee, 24);
|
||||
addChar(149, 0x7fffe1, 23);
|
||||
addChar(150, 0x7fffe2, 23);
|
||||
addChar(151, 0x7fffe3, 23);
|
||||
addChar(152, 0x7fffe4, 23);
|
||||
addChar(153, 0x1fffdc, 21);
|
||||
addChar(154, 0x3fffd8, 22);
|
||||
addChar(155, 0x7fffe5, 23);
|
||||
addChar(156, 0x3fffd9, 22);
|
||||
addChar(157, 0x7fffe6, 23);
|
||||
addChar(158, 0x7fffe7, 23);
|
||||
addChar(159, 0xffffef, 24);
|
||||
addChar(160, 0x3fffda, 22);
|
||||
addChar(161, 0x1fffdd, 21);
|
||||
addChar(162, 0xfffe9, 20);
|
||||
addChar(163, 0x3fffdb, 22);
|
||||
addChar(164, 0x3fffdc, 22);
|
||||
addChar(165, 0x7fffe8, 23);
|
||||
addChar(166, 0x7fffe9, 23);
|
||||
addChar(167, 0x1fffde, 21);
|
||||
addChar(168, 0x7fffea, 23);
|
||||
addChar(169, 0x3fffdd, 22);
|
||||
addChar(170, 0x3fffde, 22);
|
||||
addChar(171, 0xfffff0, 24);
|
||||
addChar(172, 0x1fffdf, 21);
|
||||
addChar(173, 0x3fffdf, 22);
|
||||
addChar(174, 0x7fffeb, 23);
|
||||
addChar(175, 0x7fffec, 23);
|
||||
addChar(176, 0x1fffe0, 21);
|
||||
addChar(177, 0x1fffe1, 21);
|
||||
addChar(178, 0x3fffe0, 22);
|
||||
addChar(179, 0x1fffe2, 21);
|
||||
addChar(180, 0x7fffed, 23);
|
||||
addChar(181, 0x3fffe1, 22);
|
||||
addChar(182, 0x7fffee, 23);
|
||||
addChar(183, 0x7fffef, 23);
|
||||
addChar(184, 0xfffea, 20);
|
||||
addChar(185, 0x3fffe2, 22);
|
||||
addChar(186, 0x3fffe3, 22);
|
||||
addChar(187, 0x3fffe4, 22);
|
||||
addChar(188, 0x7ffff0, 23);
|
||||
addChar(189, 0x3fffe5, 22);
|
||||
addChar(190, 0x3fffe6, 22);
|
||||
addChar(191, 0x7ffff1, 23);
|
||||
addChar(192, 0x3ffffe0, 26);
|
||||
addChar(193, 0x3ffffe1, 26);
|
||||
addChar(194, 0xfffeb, 20);
|
||||
addChar(195, 0x7fff1, 19);
|
||||
addChar(196, 0x3fffe7, 22);
|
||||
addChar(197, 0x7ffff2, 23);
|
||||
addChar(198, 0x3fffe8, 22);
|
||||
addChar(199, 0x1ffffec, 25);
|
||||
addChar(200, 0x3ffffe2, 26);
|
||||
addChar(201, 0x3ffffe3, 26);
|
||||
addChar(202, 0x3ffffe4, 26);
|
||||
addChar(203, 0x7ffffde, 27);
|
||||
addChar(204, 0x7ffffdf, 27);
|
||||
addChar(205, 0x3ffffe5, 26);
|
||||
addChar(206, 0xfffff1, 24);
|
||||
addChar(207, 0x1ffffed, 25);
|
||||
addChar(208, 0x7fff2, 19);
|
||||
addChar(209, 0x1fffe3, 21);
|
||||
addChar(210, 0x3ffffe6, 26);
|
||||
addChar(211, 0x7ffffe0, 27);
|
||||
addChar(212, 0x7ffffe1, 27);
|
||||
addChar(213, 0x3ffffe7, 26);
|
||||
addChar(214, 0x7ffffe2, 27);
|
||||
addChar(215, 0xfffff2, 24);
|
||||
addChar(216, 0x1fffe4, 21);
|
||||
addChar(217, 0x1fffe5, 21);
|
||||
addChar(218, 0x3ffffe8, 26);
|
||||
addChar(219, 0x3ffffe9, 26);
|
||||
addChar(220, 0xffffffd, 28);
|
||||
addChar(221, 0x7ffffe3, 27);
|
||||
addChar(222, 0x7ffffe4, 27);
|
||||
addChar(223, 0x7ffffe5, 27);
|
||||
addChar(224, 0xfffec, 20);
|
||||
addChar(225, 0xfffff3, 24);
|
||||
addChar(226, 0xfffed, 20);
|
||||
addChar(227, 0x1fffe6, 21);
|
||||
addChar(228, 0x3fffe9, 22);
|
||||
addChar(229, 0x1fffe7, 21);
|
||||
addChar(230, 0x1fffe8, 21);
|
||||
addChar(231, 0x7ffff3, 23);
|
||||
addChar(232, 0x3fffea, 22);
|
||||
addChar(233, 0x3fffeb, 22);
|
||||
addChar(234, 0x1ffffee, 25);
|
||||
addChar(235, 0x1ffffef, 25);
|
||||
addChar(236, 0xfffff4, 24);
|
||||
addChar(237, 0xfffff5, 24);
|
||||
addChar(238, 0x3ffffea, 26);
|
||||
addChar(239, 0x7ffff4, 23);
|
||||
addChar(240, 0x3ffffeb, 26);
|
||||
addChar(241, 0x7ffffe6, 27);
|
||||
addChar(242, 0x3ffffec, 26);
|
||||
addChar(243, 0x3ffffed, 26);
|
||||
addChar(244, 0x7ffffe7, 27);
|
||||
addChar(245, 0x7ffffe8, 27);
|
||||
addChar(246, 0x7ffffe9, 27);
|
||||
addChar(247, 0x7ffffea, 27);
|
||||
addChar(248, 0x7ffffeb, 27);
|
||||
addChar(249, 0xffffffe, 28);
|
||||
addChar(250, 0x7ffffec, 27);
|
||||
addChar(251, 0x7ffffed, 27);
|
||||
addChar(252, 0x7ffffee, 27);
|
||||
addChar(253, 0x7ffffef, 27);
|
||||
addChar(254, 0x7fffff0, 27);
|
||||
addChar(255, 0x3ffffee, 26);
|
||||
addEOS (256, EOS.code, EOS.length);
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculates the number of bytes required to represent the given {@code
|
||||
* CharSequence} with the Huffman coding.
|
||||
*
|
||||
* @param value
|
||||
* characters
|
||||
*
|
||||
* @return number of bytes
|
||||
*
|
||||
* @throws NullPointerException
|
||||
* if the value is null
|
||||
*/
|
||||
public int lengthOf(CharSequence value) {
|
||||
return lengthOf(value, 0, value.length());
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the number of bytes required to represent a subsequence of the
|
||||
* given {@code CharSequence} with the Huffman coding.
|
||||
*
|
||||
* @param value
|
||||
* characters
|
||||
* @param start
|
||||
* the start index, inclusive
|
||||
* @param end
|
||||
* the end index, exclusive
|
||||
*
|
||||
* @return number of bytes
|
||||
*
|
||||
* @throws NullPointerException
|
||||
* if the value is null
|
||||
* @throws IndexOutOfBoundsException
|
||||
* if any invocation of {@code value.charAt(i)}, where
|
||||
* {@code start <= i < end} would throw an IndexOutOfBoundsException
|
||||
*/
|
||||
public int lengthOf(CharSequence value, int start, int end) {
|
||||
int len = 0;
|
||||
for (int i = start; i < end; i++) {
|
||||
char c = value.charAt(i);
|
||||
len += INSTANCE.codeOf(c).length;
|
||||
}
|
||||
// Integer division with ceiling, assumption:
|
||||
assert (len / 8 + (len % 8 != 0 ? 1 : 0)) == (len + 7) / 8 : len;
|
||||
return (len + 7) / 8;
|
||||
}
|
||||
|
||||
private void addChar(int c, int code, int bitLength) {
|
||||
addLeaf(c, code, bitLength, false);
|
||||
codes[c] = new Code(code, bitLength);
|
||||
}
|
||||
|
||||
private void addEOS(int c, int code, int bitLength) {
|
||||
addLeaf(c, code, bitLength, true);
|
||||
codes[c] = new Code(code, bitLength);
|
||||
}
|
||||
|
||||
private void addLeaf(int c, int code, int bitLength, boolean isEOS) {
|
||||
if (bitLength < 1) {
|
||||
throw new IllegalArgumentException("bitLength < 1");
|
||||
}
|
||||
Node curr = root;
|
||||
for (int p = 1 << bitLength - 1; p != 0 && !curr.isLeaf(); p = p >> 1) {
|
||||
curr.isEOSPath |= isEOS; // If it's already true, it can't become false
|
||||
curr = curr.addChildIfAbsent(p & code);
|
||||
}
|
||||
curr.isEOSPath |= isEOS; // The last one needs to have this property as well
|
||||
if (curr.isLeaf()) {
|
||||
throw new IllegalStateException("Specified code is already taken");
|
||||
}
|
||||
curr.setChar((char) c);
|
||||
}
|
||||
|
||||
private Code codeOf(char c) {
|
||||
if (c > 255) {
|
||||
throw new IllegalArgumentException("char=" + ((int) c));
|
||||
}
|
||||
return codes[c];
|
||||
}
|
||||
|
||||
//
|
||||
// For debugging/testing purposes
|
||||
//
|
||||
Node getRoot() {
|
||||
return root;
|
||||
}
|
||||
|
||||
//
|
||||
// Guarantees:
|
||||
//
|
||||
// if (isLeaf() == true) => getChar() is a legal call
|
||||
// if (isLeaf() == false) => getChild(i) is a legal call (though it can
|
||||
// return null)
|
||||
//
|
||||
static class Node {
|
||||
|
||||
Node left;
|
||||
Node right;
|
||||
boolean isEOSPath;
|
||||
|
||||
boolean charIsSet;
|
||||
char c;
|
||||
|
||||
Node getChild(int selector) {
|
||||
if (isLeaf()) {
|
||||
throw new IllegalStateException("This is a leaf node");
|
||||
}
|
||||
Node result = selector == 0 ? left : right;
|
||||
if (result == null) {
|
||||
throw new IllegalStateException(format(
|
||||
"Node doesn't have a child (selector=%s)", selector));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
boolean isLeaf() {
|
||||
return charIsSet;
|
||||
}
|
||||
|
||||
char getChar() {
|
||||
if (!isLeaf()) {
|
||||
throw new IllegalStateException("This node is not a leaf node");
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
void setChar(char c) {
|
||||
if (charIsSet) {
|
||||
throw new IllegalStateException(
|
||||
"This node has been taken already");
|
||||
}
|
||||
if (left != null || right != null) {
|
||||
throw new IllegalStateException("The node cannot be made "
|
||||
+ "a leaf as it's already has a child");
|
||||
}
|
||||
this.c = c;
|
||||
charIsSet = true;
|
||||
}
|
||||
|
||||
Node addChildIfAbsent(int i) {
|
||||
if (charIsSet) {
|
||||
throw new IllegalStateException("The node cannot have a child "
|
||||
+ "as it's already a leaf node");
|
||||
}
|
||||
Node child;
|
||||
if (i == 0) {
|
||||
if ((child = left) == null) {
|
||||
child = left = new Node();
|
||||
}
|
||||
} else {
|
||||
if ((child = right) == null) {
|
||||
child = right = new Node();
|
||||
}
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (isLeaf()) {
|
||||
if (isEOSPath) {
|
||||
return "EOS";
|
||||
} else {
|
||||
return format("char: (%3s) '%s'", (int) c, c);
|
||||
}
|
||||
}
|
||||
return "/\\";
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: value-based class?
|
||||
// FIXME: can we re-use Node instead of this class?
|
||||
private static final class Code {
|
||||
|
||||
final int code;
|
||||
final int length;
|
||||
|
||||
private Code(int code, int length) {
|
||||
this.code = code;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public int getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
long p = 1 << length;
|
||||
return Long.toBinaryString(code + p).substring(1)
|
||||
+ ", length=" + length;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,826 @@
|
|||
/*
|
||||
* Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.internal.net.http.hpack;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public final class QuickHuffman {
|
||||
|
||||
/*
|
||||
* Huffman codes for encoding.
|
||||
*
|
||||
* EOS will never be matched, since there is no symbol for it, thus no need
|
||||
* to store it in the array. Thus, the length of the array is 256, not 257.
|
||||
* Code information for each character is encoded as follows:
|
||||
*
|
||||
* MSB LSB
|
||||
* +----------------+----------------+
|
||||
* |~code | length~|
|
||||
* +----------------+----------------+
|
||||
* |<----- 32 ----->|<----- 32 ----->|
|
||||
* |<------------- 64 -------------->|
|
||||
*
|
||||
* The leftmost 32 bits hold the code value. This value is aligned left
|
||||
* (or MSB). The rightmost 32 bits hold the length of the code value.
|
||||
* This length is aligned right (or LSB).
|
||||
*/
|
||||
private static final long[] codes = new long[256];
|
||||
|
||||
private static long codeValueOf(char c) {
|
||||
return codes[c] & 0xffffffff00000000L;
|
||||
}
|
||||
|
||||
private static long codeLengthOf(char c) {
|
||||
return codes[c] & 0x00000000ffffffffL;
|
||||
}
|
||||
|
||||
private static final int EOS_LENGTH = 30;
|
||||
private static final int EOS_LSB = 0x3fffffff;
|
||||
private static final long EOS_MSB = EOS_LSB << (64 - EOS_LENGTH);
|
||||
|
||||
/*
|
||||
* Huffman codes for decoding.
|
||||
*
|
||||
* Root node contains 257 descendant nodes, including EOS.
|
||||
*/
|
||||
private static final Node root;
|
||||
|
||||
static {
|
||||
TemporaryNode tmpRoot = new TemporaryNode();
|
||||
addChar(tmpRoot, 0, 0x1ff8, 13);
|
||||
addChar(tmpRoot, 1, 0x7fffd8, 23);
|
||||
addChar(tmpRoot, 2, 0xfffffe2, 28);
|
||||
addChar(tmpRoot, 3, 0xfffffe3, 28);
|
||||
addChar(tmpRoot, 4, 0xfffffe4, 28);
|
||||
addChar(tmpRoot, 5, 0xfffffe5, 28);
|
||||
addChar(tmpRoot, 6, 0xfffffe6, 28);
|
||||
addChar(tmpRoot, 7, 0xfffffe7, 28);
|
||||
addChar(tmpRoot, 8, 0xfffffe8, 28);
|
||||
addChar(tmpRoot, 9, 0xffffea, 24);
|
||||
addChar(tmpRoot, 10, 0x3ffffffc, 30);
|
||||
addChar(tmpRoot, 11, 0xfffffe9, 28);
|
||||
addChar(tmpRoot, 12, 0xfffffea, 28);
|
||||
addChar(tmpRoot, 13, 0x3ffffffd, 30);
|
||||
addChar(tmpRoot, 14, 0xfffffeb, 28);
|
||||
addChar(tmpRoot, 15, 0xfffffec, 28);
|
||||
addChar(tmpRoot, 16, 0xfffffed, 28);
|
||||
addChar(tmpRoot, 17, 0xfffffee, 28);
|
||||
addChar(tmpRoot, 18, 0xfffffef, 28);
|
||||
addChar(tmpRoot, 19, 0xffffff0, 28);
|
||||
addChar(tmpRoot, 20, 0xffffff1, 28);
|
||||
addChar(tmpRoot, 21, 0xffffff2, 28);
|
||||
addChar(tmpRoot, 22, 0x3ffffffe, 30);
|
||||
addChar(tmpRoot, 23, 0xffffff3, 28);
|
||||
addChar(tmpRoot, 24, 0xffffff4, 28);
|
||||
addChar(tmpRoot, 25, 0xffffff5, 28);
|
||||
addChar(tmpRoot, 26, 0xffffff6, 28);
|
||||
addChar(tmpRoot, 27, 0xffffff7, 28);
|
||||
addChar(tmpRoot, 28, 0xffffff8, 28);
|
||||
addChar(tmpRoot, 29, 0xffffff9, 28);
|
||||
addChar(tmpRoot, 30, 0xffffffa, 28);
|
||||
addChar(tmpRoot, 31, 0xffffffb, 28);
|
||||
addChar(tmpRoot, 32, 0x14, 6);
|
||||
addChar(tmpRoot, 33, 0x3f8, 10);
|
||||
addChar(tmpRoot, 34, 0x3f9, 10);
|
||||
addChar(tmpRoot, 35, 0xffa, 12);
|
||||
addChar(tmpRoot, 36, 0x1ff9, 13);
|
||||
addChar(tmpRoot, 37, 0x15, 6);
|
||||
addChar(tmpRoot, 38, 0xf8, 8);
|
||||
addChar(tmpRoot, 39, 0x7fa, 11);
|
||||
addChar(tmpRoot, 40, 0x3fa, 10);
|
||||
addChar(tmpRoot, 41, 0x3fb, 10);
|
||||
addChar(tmpRoot, 42, 0xf9, 8);
|
||||
addChar(tmpRoot, 43, 0x7fb, 11);
|
||||
addChar(tmpRoot, 44, 0xfa, 8);
|
||||
addChar(tmpRoot, 45, 0x16, 6);
|
||||
addChar(tmpRoot, 46, 0x17, 6);
|
||||
addChar(tmpRoot, 47, 0x18, 6);
|
||||
addChar(tmpRoot, 48, 0x0, 5);
|
||||
addChar(tmpRoot, 49, 0x1, 5);
|
||||
addChar(tmpRoot, 50, 0x2, 5);
|
||||
addChar(tmpRoot, 51, 0x19, 6);
|
||||
addChar(tmpRoot, 52, 0x1a, 6);
|
||||
addChar(tmpRoot, 53, 0x1b, 6);
|
||||
addChar(tmpRoot, 54, 0x1c, 6);
|
||||
addChar(tmpRoot, 55, 0x1d, 6);
|
||||
addChar(tmpRoot, 56, 0x1e, 6);
|
||||
addChar(tmpRoot, 57, 0x1f, 6);
|
||||
addChar(tmpRoot, 58, 0x5c, 7);
|
||||
addChar(tmpRoot, 59, 0xfb, 8);
|
||||
addChar(tmpRoot, 60, 0x7ffc, 15);
|
||||
addChar(tmpRoot, 61, 0x20, 6);
|
||||
addChar(tmpRoot, 62, 0xffb, 12);
|
||||
addChar(tmpRoot, 63, 0x3fc, 10);
|
||||
addChar(tmpRoot, 64, 0x1ffa, 13);
|
||||
addChar(tmpRoot, 65, 0x21, 6);
|
||||
addChar(tmpRoot, 66, 0x5d, 7);
|
||||
addChar(tmpRoot, 67, 0x5e, 7);
|
||||
addChar(tmpRoot, 68, 0x5f, 7);
|
||||
addChar(tmpRoot, 69, 0x60, 7);
|
||||
addChar(tmpRoot, 70, 0x61, 7);
|
||||
addChar(tmpRoot, 71, 0x62, 7);
|
||||
addChar(tmpRoot, 72, 0x63, 7);
|
||||
addChar(tmpRoot, 73, 0x64, 7);
|
||||
addChar(tmpRoot, 74, 0x65, 7);
|
||||
addChar(tmpRoot, 75, 0x66, 7);
|
||||
addChar(tmpRoot, 76, 0x67, 7);
|
||||
addChar(tmpRoot, 77, 0x68, 7);
|
||||
addChar(tmpRoot, 78, 0x69, 7);
|
||||
addChar(tmpRoot, 79, 0x6a, 7);
|
||||
addChar(tmpRoot, 80, 0x6b, 7);
|
||||
addChar(tmpRoot, 81, 0x6c, 7);
|
||||
addChar(tmpRoot, 82, 0x6d, 7);
|
||||
addChar(tmpRoot, 83, 0x6e, 7);
|
||||
addChar(tmpRoot, 84, 0x6f, 7);
|
||||
addChar(tmpRoot, 85, 0x70, 7);
|
||||
addChar(tmpRoot, 86, 0x71, 7);
|
||||
addChar(tmpRoot, 87, 0x72, 7);
|
||||
addChar(tmpRoot, 88, 0xfc, 8);
|
||||
addChar(tmpRoot, 89, 0x73, 7);
|
||||
addChar(tmpRoot, 90, 0xfd, 8);
|
||||
addChar(tmpRoot, 91, 0x1ffb, 13);
|
||||
addChar(tmpRoot, 92, 0x7fff0, 19);
|
||||
addChar(tmpRoot, 93, 0x1ffc, 13);
|
||||
addChar(tmpRoot, 94, 0x3ffc, 14);
|
||||
addChar(tmpRoot, 95, 0x22, 6);
|
||||
addChar(tmpRoot, 96, 0x7ffd, 15);
|
||||
addChar(tmpRoot, 97, 0x3, 5);
|
||||
addChar(tmpRoot, 98, 0x23, 6);
|
||||
addChar(tmpRoot, 99, 0x4, 5);
|
||||
addChar(tmpRoot, 100, 0x24, 6);
|
||||
addChar(tmpRoot, 101, 0x5, 5);
|
||||
addChar(tmpRoot, 102, 0x25, 6);
|
||||
addChar(tmpRoot, 103, 0x26, 6);
|
||||
addChar(tmpRoot, 104, 0x27, 6);
|
||||
addChar(tmpRoot, 105, 0x6, 5);
|
||||
addChar(tmpRoot, 106, 0x74, 7);
|
||||
addChar(tmpRoot, 107, 0x75, 7);
|
||||
addChar(tmpRoot, 108, 0x28, 6);
|
||||
addChar(tmpRoot, 109, 0x29, 6);
|
||||
addChar(tmpRoot, 110, 0x2a, 6);
|
||||
addChar(tmpRoot, 111, 0x7, 5);
|
||||
addChar(tmpRoot, 112, 0x2b, 6);
|
||||
addChar(tmpRoot, 113, 0x76, 7);
|
||||
addChar(tmpRoot, 114, 0x2c, 6);
|
||||
addChar(tmpRoot, 115, 0x8, 5);
|
||||
addChar(tmpRoot, 116, 0x9, 5);
|
||||
addChar(tmpRoot, 117, 0x2d, 6);
|
||||
addChar(tmpRoot, 118, 0x77, 7);
|
||||
addChar(tmpRoot, 119, 0x78, 7);
|
||||
addChar(tmpRoot, 120, 0x79, 7);
|
||||
addChar(tmpRoot, 121, 0x7a, 7);
|
||||
addChar(tmpRoot, 122, 0x7b, 7);
|
||||
addChar(tmpRoot, 123, 0x7ffe, 15);
|
||||
addChar(tmpRoot, 124, 0x7fc, 11);
|
||||
addChar(tmpRoot, 125, 0x3ffd, 14);
|
||||
addChar(tmpRoot, 126, 0x1ffd, 13);
|
||||
addChar(tmpRoot, 127, 0xffffffc, 28);
|
||||
addChar(tmpRoot, 128, 0xfffe6, 20);
|
||||
addChar(tmpRoot, 129, 0x3fffd2, 22);
|
||||
addChar(tmpRoot, 130, 0xfffe7, 20);
|
||||
addChar(tmpRoot, 131, 0xfffe8, 20);
|
||||
addChar(tmpRoot, 132, 0x3fffd3, 22);
|
||||
addChar(tmpRoot, 133, 0x3fffd4, 22);
|
||||
addChar(tmpRoot, 134, 0x3fffd5, 22);
|
||||
addChar(tmpRoot, 135, 0x7fffd9, 23);
|
||||
addChar(tmpRoot, 136, 0x3fffd6, 22);
|
||||
addChar(tmpRoot, 137, 0x7fffda, 23);
|
||||
addChar(tmpRoot, 138, 0x7fffdb, 23);
|
||||
addChar(tmpRoot, 139, 0x7fffdc, 23);
|
||||
addChar(tmpRoot, 140, 0x7fffdd, 23);
|
||||
addChar(tmpRoot, 141, 0x7fffde, 23);
|
||||
addChar(tmpRoot, 142, 0xffffeb, 24);
|
||||
addChar(tmpRoot, 143, 0x7fffdf, 23);
|
||||
addChar(tmpRoot, 144, 0xffffec, 24);
|
||||
addChar(tmpRoot, 145, 0xffffed, 24);
|
||||
addChar(tmpRoot, 146, 0x3fffd7, 22);
|
||||
addChar(tmpRoot, 147, 0x7fffe0, 23);
|
||||
addChar(tmpRoot, 148, 0xffffee, 24);
|
||||
addChar(tmpRoot, 149, 0x7fffe1, 23);
|
||||
addChar(tmpRoot, 150, 0x7fffe2, 23);
|
||||
addChar(tmpRoot, 151, 0x7fffe3, 23);
|
||||
addChar(tmpRoot, 152, 0x7fffe4, 23);
|
||||
addChar(tmpRoot, 153, 0x1fffdc, 21);
|
||||
addChar(tmpRoot, 154, 0x3fffd8, 22);
|
||||
addChar(tmpRoot, 155, 0x7fffe5, 23);
|
||||
addChar(tmpRoot, 156, 0x3fffd9, 22);
|
||||
addChar(tmpRoot, 157, 0x7fffe6, 23);
|
||||
addChar(tmpRoot, 158, 0x7fffe7, 23);
|
||||
addChar(tmpRoot, 159, 0xffffef, 24);
|
||||
addChar(tmpRoot, 160, 0x3fffda, 22);
|
||||
addChar(tmpRoot, 161, 0x1fffdd, 21);
|
||||
addChar(tmpRoot, 162, 0xfffe9, 20);
|
||||
addChar(tmpRoot, 163, 0x3fffdb, 22);
|
||||
addChar(tmpRoot, 164, 0x3fffdc, 22);
|
||||
addChar(tmpRoot, 165, 0x7fffe8, 23);
|
||||
addChar(tmpRoot, 166, 0x7fffe9, 23);
|
||||
addChar(tmpRoot, 167, 0x1fffde, 21);
|
||||
addChar(tmpRoot, 168, 0x7fffea, 23);
|
||||
addChar(tmpRoot, 169, 0x3fffdd, 22);
|
||||
addChar(tmpRoot, 170, 0x3fffde, 22);
|
||||
addChar(tmpRoot, 171, 0xfffff0, 24);
|
||||
addChar(tmpRoot, 172, 0x1fffdf, 21);
|
||||
addChar(tmpRoot, 173, 0x3fffdf, 22);
|
||||
addChar(tmpRoot, 174, 0x7fffeb, 23);
|
||||
addChar(tmpRoot, 175, 0x7fffec, 23);
|
||||
addChar(tmpRoot, 176, 0x1fffe0, 21);
|
||||
addChar(tmpRoot, 177, 0x1fffe1, 21);
|
||||
addChar(tmpRoot, 178, 0x3fffe0, 22);
|
||||
addChar(tmpRoot, 179, 0x1fffe2, 21);
|
||||
addChar(tmpRoot, 180, 0x7fffed, 23);
|
||||
addChar(tmpRoot, 181, 0x3fffe1, 22);
|
||||
addChar(tmpRoot, 182, 0x7fffee, 23);
|
||||
addChar(tmpRoot, 183, 0x7fffef, 23);
|
||||
addChar(tmpRoot, 184, 0xfffea, 20);
|
||||
addChar(tmpRoot, 185, 0x3fffe2, 22);
|
||||
addChar(tmpRoot, 186, 0x3fffe3, 22);
|
||||
addChar(tmpRoot, 187, 0x3fffe4, 22);
|
||||
addChar(tmpRoot, 188, 0x7ffff0, 23);
|
||||
addChar(tmpRoot, 189, 0x3fffe5, 22);
|
||||
addChar(tmpRoot, 190, 0x3fffe6, 22);
|
||||
addChar(tmpRoot, 191, 0x7ffff1, 23);
|
||||
addChar(tmpRoot, 192, 0x3ffffe0, 26);
|
||||
addChar(tmpRoot, 193, 0x3ffffe1, 26);
|
||||
addChar(tmpRoot, 194, 0xfffeb, 20);
|
||||
addChar(tmpRoot, 195, 0x7fff1, 19);
|
||||
addChar(tmpRoot, 196, 0x3fffe7, 22);
|
||||
addChar(tmpRoot, 197, 0x7ffff2, 23);
|
||||
addChar(tmpRoot, 198, 0x3fffe8, 22);
|
||||
addChar(tmpRoot, 199, 0x1ffffec, 25);
|
||||
addChar(tmpRoot, 200, 0x3ffffe2, 26);
|
||||
addChar(tmpRoot, 201, 0x3ffffe3, 26);
|
||||
addChar(tmpRoot, 202, 0x3ffffe4, 26);
|
||||
addChar(tmpRoot, 203, 0x7ffffde, 27);
|
||||
addChar(tmpRoot, 204, 0x7ffffdf, 27);
|
||||
addChar(tmpRoot, 205, 0x3ffffe5, 26);
|
||||
addChar(tmpRoot, 206, 0xfffff1, 24);
|
||||
addChar(tmpRoot, 207, 0x1ffffed, 25);
|
||||
addChar(tmpRoot, 208, 0x7fff2, 19);
|
||||
addChar(tmpRoot, 209, 0x1fffe3, 21);
|
||||
addChar(tmpRoot, 210, 0x3ffffe6, 26);
|
||||
addChar(tmpRoot, 211, 0x7ffffe0, 27);
|
||||
addChar(tmpRoot, 212, 0x7ffffe1, 27);
|
||||
addChar(tmpRoot, 213, 0x3ffffe7, 26);
|
||||
addChar(tmpRoot, 214, 0x7ffffe2, 27);
|
||||
addChar(tmpRoot, 215, 0xfffff2, 24);
|
||||
addChar(tmpRoot, 216, 0x1fffe4, 21);
|
||||
addChar(tmpRoot, 217, 0x1fffe5, 21);
|
||||
addChar(tmpRoot, 218, 0x3ffffe8, 26);
|
||||
addChar(tmpRoot, 219, 0x3ffffe9, 26);
|
||||
addChar(tmpRoot, 220, 0xffffffd, 28);
|
||||
addChar(tmpRoot, 221, 0x7ffffe3, 27);
|
||||
addChar(tmpRoot, 222, 0x7ffffe4, 27);
|
||||
addChar(tmpRoot, 223, 0x7ffffe5, 27);
|
||||
addChar(tmpRoot, 224, 0xfffec, 20);
|
||||
addChar(tmpRoot, 225, 0xfffff3, 24);
|
||||
addChar(tmpRoot, 226, 0xfffed, 20);
|
||||
addChar(tmpRoot, 227, 0x1fffe6, 21);
|
||||
addChar(tmpRoot, 228, 0x3fffe9, 22);
|
||||
addChar(tmpRoot, 229, 0x1fffe7, 21);
|
||||
addChar(tmpRoot, 230, 0x1fffe8, 21);
|
||||
addChar(tmpRoot, 231, 0x7ffff3, 23);
|
||||
addChar(tmpRoot, 232, 0x3fffea, 22);
|
||||
addChar(tmpRoot, 233, 0x3fffeb, 22);
|
||||
addChar(tmpRoot, 234, 0x1ffffee, 25);
|
||||
addChar(tmpRoot, 235, 0x1ffffef, 25);
|
||||
addChar(tmpRoot, 236, 0xfffff4, 24);
|
||||
addChar(tmpRoot, 237, 0xfffff5, 24);
|
||||
addChar(tmpRoot, 238, 0x3ffffea, 26);
|
||||
addChar(tmpRoot, 239, 0x7ffff4, 23);
|
||||
addChar(tmpRoot, 240, 0x3ffffeb, 26);
|
||||
addChar(tmpRoot, 241, 0x7ffffe6, 27);
|
||||
addChar(tmpRoot, 242, 0x3ffffec, 26);
|
||||
addChar(tmpRoot, 243, 0x3ffffed, 26);
|
||||
addChar(tmpRoot, 244, 0x7ffffe7, 27);
|
||||
addChar(tmpRoot, 245, 0x7ffffe8, 27);
|
||||
addChar(tmpRoot, 246, 0x7ffffe9, 27);
|
||||
addChar(tmpRoot, 247, 0x7ffffea, 27);
|
||||
addChar(tmpRoot, 248, 0x7ffffeb, 27);
|
||||
addChar(tmpRoot, 249, 0xffffffe, 28);
|
||||
addChar(tmpRoot, 250, 0x7ffffec, 27);
|
||||
addChar(tmpRoot, 251, 0x7ffffed, 27);
|
||||
addChar(tmpRoot, 252, 0x7ffffee, 27);
|
||||
addChar(tmpRoot, 253, 0x7ffffef, 27);
|
||||
addChar(tmpRoot, 254, 0x7fffff0, 27);
|
||||
addChar(tmpRoot, 255, 0x3ffffee, 26);
|
||||
addEOS (tmpRoot, 256, EOS_LSB, EOS_LENGTH);
|
||||
|
||||
// The difference in performance can always be checked by not using
|
||||
// the immutable trie:
|
||||
// root = tmpRoot;
|
||||
root = ImmutableNode.copyOf(tmpRoot);
|
||||
}
|
||||
|
||||
private QuickHuffman() { }
|
||||
|
||||
private static void addChar(Node root, int symbol, int code, int bitLength)
|
||||
{
|
||||
addLeaf(root, (char) symbol, code, bitLength, false);
|
||||
long value = ((long) code) << (64 - bitLength); // re-align MSB <- LSB
|
||||
codes[symbol] = value | bitLength;
|
||||
}
|
||||
|
||||
private static void addEOS(Node root, int symbol, int code, int bitLength)
|
||||
{
|
||||
addLeaf(root, (char) symbol, code, bitLength, true);
|
||||
}
|
||||
|
||||
private static void addLeaf(Node root,
|
||||
char symbol,
|
||||
int code,
|
||||
int bitLength,
|
||||
boolean isEOS)
|
||||
{
|
||||
assert 0 < bitLength && bitLength <= 32 : bitLength;
|
||||
Node curr = root;
|
||||
int nBytes = bytesForBits(bitLength);
|
||||
// The number of bits the code needs to be shifted to the left in order
|
||||
// to align with the byte #nBytes boundary:
|
||||
int align = (nBytes << 3) - bitLength;
|
||||
code <<= align;
|
||||
// descend the trie until the last element
|
||||
int l = 0;
|
||||
for (int i = 0, probe = 0xff << ((nBytes - 1) << 3);
|
||||
i < nBytes - 1;
|
||||
i++, probe >>>= 8)
|
||||
{
|
||||
curr.setEOSPath(curr.isEOSPath() | isEOS);
|
||||
int idx = (code & probe) >>> ((nBytes - 1 - i) << 3);
|
||||
curr = curr.getOrCreateChild(idx);
|
||||
curr.setLength(8);
|
||||
l += 8;
|
||||
}
|
||||
// Assign the same char to all byte variants. For example, if the code
|
||||
// and its length are 00011b and 5 respectively (letter 'a') then, in
|
||||
// order to be able to match any byte starting with 00011b prefix,
|
||||
// the following nodes need to be created:
|
||||
//
|
||||
// 00011000b, 00011001b, 00011010b, 00011011b, 00011100b, 00011101b,
|
||||
// 00011110b and 00011111b
|
||||
int idx = code & 0xff;
|
||||
curr.setEOSPath(curr.isEOSPath() | isEOS);
|
||||
for (int i = 0; i < (1 << align); i++) {
|
||||
Node child = curr.getOrCreateChild(idx | i);
|
||||
child.setSymbol(symbol);
|
||||
child.setEOSPath(child.isEOSPath() | isEOS);
|
||||
child.setLength(bitLength - l);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* A node in the Huffman trie.
|
||||
*/
|
||||
interface Node {
|
||||
|
||||
boolean isEOSPath();
|
||||
|
||||
void setEOSPath(boolean value);
|
||||
|
||||
boolean isLeaf();
|
||||
|
||||
Node getChild(int index);
|
||||
|
||||
Node getOrCreateChild(int index);
|
||||
|
||||
Node[] getChildren();
|
||||
|
||||
char getSymbol();
|
||||
|
||||
void setSymbol(char symbol);
|
||||
|
||||
int getLength();
|
||||
|
||||
void setLength(int value);
|
||||
}
|
||||
|
||||
/*
|
||||
* Mutable nodes used for initial construction of the Huffman trie.
|
||||
* (These nodes are perfectly ok to be used after that.)
|
||||
*/
|
||||
static final class TemporaryNode implements Node {
|
||||
|
||||
private char symbol;
|
||||
private boolean eosPath;
|
||||
private TemporaryNode[] children;
|
||||
private int length;
|
||||
|
||||
@Override
|
||||
public TemporaryNode getOrCreateChild(int index) {
|
||||
ensureChildrenExist();
|
||||
if (children[index] == null) {
|
||||
children[index] = new TemporaryNode();
|
||||
}
|
||||
return children[index];
|
||||
}
|
||||
|
||||
private void ensureChildrenExist() {
|
||||
if (children == null) {
|
||||
children = new TemporaryNode[256];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLeaf() {
|
||||
return children == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEOSPath() {
|
||||
return eosPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEOSPath(boolean value) {
|
||||
eosPath = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TemporaryNode getChild(int index) {
|
||||
ensureChildrenExist();
|
||||
return children[index];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node[] getChildren() {
|
||||
if (children == null) {
|
||||
return new Node[0];
|
||||
}
|
||||
return children;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getSymbol() {
|
||||
return symbol;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSymbol(char value) {
|
||||
this.symbol = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLength(int value) {
|
||||
this.length = value;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Immutable node used to construct traversal-only Huffman trie.
|
||||
*
|
||||
* Once the trie has been built, the support of modifications is no longer
|
||||
* required. An immutable trie should be used. Not only it will help to
|
||||
* catch possible bugs, but hopefully speedup the traversal operations.
|
||||
*/
|
||||
static final class ImmutableNode implements Node {
|
||||
|
||||
private final char symbol;
|
||||
private final boolean eosPath;
|
||||
private final int length;
|
||||
private final List<ImmutableNode> children;
|
||||
|
||||
public static ImmutableNode copyOf(Node node) {
|
||||
if (node.isLeaf()) {
|
||||
return new ImmutableNode(node.getSymbol(), node.isEOSPath(),
|
||||
node.getLength());
|
||||
}
|
||||
Node[] children = node.getChildren();
|
||||
ImmutableNode[] immutableChildren = new ImmutableNode[children.length];
|
||||
for (int i = 0; i < children.length; i++) {
|
||||
immutableChildren[i] = copyOf(children[i]);
|
||||
}
|
||||
return new ImmutableNode(node.isEOSPath(), node.getLength(),
|
||||
immutableChildren);
|
||||
}
|
||||
|
||||
/* Creates a leaf node */
|
||||
private ImmutableNode(char symbol,
|
||||
boolean eosPath,
|
||||
int length) {
|
||||
this.symbol = symbol;
|
||||
this.eosPath = eosPath;
|
||||
this.length = length;
|
||||
this.children = List.of();
|
||||
}
|
||||
|
||||
/* Creates a node with children */
|
||||
private ImmutableNode(boolean eosPath,
|
||||
int length,
|
||||
ImmutableNode[] children)
|
||||
{
|
||||
this.symbol = 0;
|
||||
this.eosPath = eosPath;
|
||||
this.length = length;
|
||||
if (children.length == 0) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
// A list produced by List.of should not be slower than array for
|
||||
// accessing elements by index, and hopefully use additional
|
||||
// optimizations (e.g. jdk.internal.vm.annotation.Stable)
|
||||
this.children = List.of(children);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLeaf() {
|
||||
return children.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEOSPath() {
|
||||
return eosPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEOSPath(boolean value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableNode getChild(int index) {
|
||||
return children.get(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableNode getOrCreateChild(int index) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableNode[] getChildren() {
|
||||
// This method is not expected to be called on an immutable node.
|
||||
// If it is called, it requires some investigation.
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getSymbol() {
|
||||
return symbol;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSymbol(char symbol) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLength(int value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
static final class Reader implements Huffman.Reader {
|
||||
|
||||
private Node curr = root; // current position in the trie
|
||||
private long buffer; // bits left from the previous match (aligned to the left, or MSB)
|
||||
private int bufferLen; // number of bits in the buffer
|
||||
private int len; // length (in bits) of path to curr
|
||||
private boolean done;
|
||||
|
||||
@Override
|
||||
public void read(ByteBuffer source,
|
||||
Appendable destination,
|
||||
boolean isLast) throws IOException
|
||||
{
|
||||
read(source, destination, true, isLast);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
curr = root;
|
||||
len = 0;
|
||||
buffer = 0;
|
||||
bufferLen = 0;
|
||||
done = false;
|
||||
}
|
||||
|
||||
@SuppressWarnings("fallthrough")
|
||||
void read(ByteBuffer source,
|
||||
Appendable destination,
|
||||
boolean reportEOS, /* reportEOS is exposed for tests */
|
||||
boolean isLast) throws IOException
|
||||
{
|
||||
while (!done) {
|
||||
// read as much as possible (up to 8 bytes)
|
||||
int remaining = source.remaining();
|
||||
int nBytes = Math.min((64 - bufferLen) >> 3, remaining);
|
||||
switch (nBytes) {
|
||||
case 0:
|
||||
break;
|
||||
case 3:
|
||||
readByte(source);
|
||||
case 2:
|
||||
readByte(source);
|
||||
case 1:
|
||||
readByte(source);
|
||||
break;
|
||||
case 7:
|
||||
readByte(source);
|
||||
case 6:
|
||||
readByte(source);
|
||||
case 5:
|
||||
readByte(source);
|
||||
case 4:
|
||||
readInt(source);
|
||||
break;
|
||||
case 8:
|
||||
readLong(source);
|
||||
break;
|
||||
default:
|
||||
throw new InternalError(String.valueOf(nBytes));
|
||||
}
|
||||
// write as much as possible
|
||||
while (true) {
|
||||
if (bufferLen < 8) {
|
||||
if (nBytes < remaining) { // read again
|
||||
break;
|
||||
} else if (!isLast) { // exit the method to accept more input
|
||||
return;
|
||||
} else if (bufferLen > 0) { // no more data is expected, pad
|
||||
// (this padding may be done more than once)
|
||||
buffer |= ((0xff00000000000000L >>> bufferLen)
|
||||
& 0xff00000000000000L);
|
||||
// do not update bufferLen, since all those ones are
|
||||
// synthetic and are appended merely for the sake of
|
||||
// lookup
|
||||
} else {
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
int idx = (int) (buffer >>> 56);
|
||||
Node node = curr.getChild(idx);
|
||||
if (node == null) { // TODO: TEST
|
||||
throw new IOException("Unexpected byte");
|
||||
}
|
||||
if (node.isLeaf()) {
|
||||
if (node.getLength() > bufferLen) { // matched more than we actually could (because of padding)
|
||||
throw new IOException(
|
||||
"Not a EOS prefix padding or unexpected end of data");
|
||||
}
|
||||
if (reportEOS && node.isEOSPath()) {
|
||||
throw new IOException("Encountered EOS");
|
||||
}
|
||||
destination.append(node.getSymbol());
|
||||
curr = root;
|
||||
len = 0;
|
||||
} else {
|
||||
curr = node;
|
||||
// because of the padding, we can't match more bits than
|
||||
// there are currently in the buffer
|
||||
len += Math.min(bufferLen, node.getLength());
|
||||
}
|
||||
buffer <<= node.getLength();
|
||||
bufferLen -= node.getLength();
|
||||
}
|
||||
if (done && (curr.isEOSPath() && len > 7)) {
|
||||
throw new IOException(
|
||||
"Padding is too long (len=" + len + ") "
|
||||
+ "or unexpected end of data");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void readLong(ByteBuffer source) {
|
||||
buffer = source.getLong();
|
||||
bufferLen = 64;
|
||||
}
|
||||
|
||||
private void readInt(ByteBuffer source) {
|
||||
long b;
|
||||
b = source.getInt() & 0x00000000ffffffffL;
|
||||
buffer |= (b << (32 - bufferLen));
|
||||
bufferLen += 32;
|
||||
}
|
||||
|
||||
private void readByte(ByteBuffer source) {
|
||||
long b = source.get() & 0x00000000000000ffL;
|
||||
buffer |= (b << (56 - bufferLen));
|
||||
bufferLen += 8;
|
||||
}
|
||||
}
|
||||
|
||||
static final class Writer implements Huffman.Writer {
|
||||
|
||||
private CharSequence source;
|
||||
private boolean padded;
|
||||
private int pos;
|
||||
private int end;
|
||||
private long buffer;
|
||||
private int bufferLen;
|
||||
|
||||
@Override
|
||||
public QuickHuffman.Writer from(CharSequence input, int start, int end) {
|
||||
Objects.checkFromToIndex(start, end, input.length());
|
||||
this.pos = start;
|
||||
this.end = end;
|
||||
this.source = input;
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("fallthrough")
|
||||
@Override
|
||||
public boolean write(ByteBuffer destination) {
|
||||
while (true) {
|
||||
while (bufferLen < 32 && pos < end) {
|
||||
char c = source.charAt(pos++);
|
||||
buffer |= (codeValueOf(c) >>> bufferLen); // append
|
||||
bufferLen += codeLengthOf(c);
|
||||
}
|
||||
if (bufferLen == 0) {
|
||||
return true;
|
||||
}
|
||||
if (pos >= end && !padded) { // no more chars, pad
|
||||
padded = true;
|
||||
buffer |= (EOS_MSB >>> bufferLen);
|
||||
bufferLen = bytesForBits(bufferLen) << 3;
|
||||
}
|
||||
// The number of bytes that can be written at once
|
||||
// (calculating in bytes, not bits, since
|
||||
// destination.remaining() * 8 might overflow)
|
||||
|
||||
int nBytes = Math.min(bytesForBits(bufferLen), destination.remaining()); // ceil?
|
||||
switch (nBytes) {
|
||||
case 0:
|
||||
return false;
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
destination.put((byte) (buffer >>> 56));
|
||||
buffer <<= 8;
|
||||
bufferLen -= 8;
|
||||
break;
|
||||
default:
|
||||
destination.putInt((int) (buffer >>> 32));
|
||||
buffer <<= 32;
|
||||
bufferLen -= 32;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuickHuffman.Writer reset() {
|
||||
source = null;
|
||||
buffer = 0;
|
||||
bufferLen = 0;
|
||||
end = 0;
|
||||
pos = 0;
|
||||
padded = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int lengthOf(CharSequence value, int start, int end) {
|
||||
int len = 0;
|
||||
for (int i = start; i < end; i++) {
|
||||
char c = value.charAt(i);
|
||||
len += codeLengthOf(c);
|
||||
}
|
||||
return bytesForBits(len);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the number of bytes the given number of bits constitute.
|
||||
*/
|
||||
private static int bytesForBits(int n) {
|
||||
assert (n / 8 + (n % 8 != 0 ? 1 : 0)) == (n + 7) / 8
|
||||
&& (n + 7) / 8 == ((n + 7) >> 3) : n;
|
||||
return (n + 7) >> 3;
|
||||
}
|
||||
}
|
|
@ -28,6 +28,7 @@ import jdk.internal.net.http.hpack.HPACK.Logger;
|
|||
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import static jdk.internal.net.http.common.Utils.pow2Size;
|
||||
import static jdk.internal.net.http.hpack.HPACK.Logger.Level.EXTRA;
|
||||
import static jdk.internal.net.http.hpack.HPACK.Logger.Level.NORMAL;
|
||||
import static java.lang.String.format;
|
||||
|
@ -306,8 +307,8 @@ class SimpleHeaderTable {
|
|||
Object[] elements;
|
||||
|
||||
CircularBuffer(int capacity) {
|
||||
this.capacity = capacity;
|
||||
elements = new Object[capacity];
|
||||
this.capacity = pow2Size(capacity);
|
||||
elements = new Object[this.capacity];
|
||||
}
|
||||
|
||||
void add(E elem) {
|
||||
|
@ -316,7 +317,7 @@ class SimpleHeaderTable {
|
|||
format("No room for '%s': capacity=%s", elem, capacity));
|
||||
}
|
||||
elements[head] = elem;
|
||||
head = (head + 1) % capacity;
|
||||
head = (head + 1) & (capacity - 1);
|
||||
size++;
|
||||
}
|
||||
|
||||
|
@ -327,7 +328,7 @@ class SimpleHeaderTable {
|
|||
}
|
||||
E elem = (E) elements[tail];
|
||||
elements[tail] = null;
|
||||
tail = (tail + 1) % capacity;
|
||||
tail = (tail + 1) & (capacity - 1);
|
||||
size--;
|
||||
return elem;
|
||||
}
|
||||
|
@ -339,7 +340,7 @@ class SimpleHeaderTable {
|
|||
format("0 <= index <= capacity: index=%s, capacity=%s",
|
||||
index, capacity));
|
||||
}
|
||||
int idx = (tail + (size - index - 1)) % capacity;
|
||||
int idx = (tail + (size - index - 1)) & (capacity - 1);
|
||||
return (E) elements[idx];
|
||||
}
|
||||
|
||||
|
@ -350,7 +351,8 @@ class SimpleHeaderTable {
|
|||
newCapacity, size));
|
||||
}
|
||||
|
||||
Object[] newElements = new Object[newCapacity];
|
||||
int capacity = pow2Size(newCapacity);
|
||||
Object[] newElements = new Object[capacity];
|
||||
|
||||
if (tail < head || size == 0) {
|
||||
System.arraycopy(elements, tail, newElements, 0, size);
|
||||
|
@ -362,7 +364,7 @@ class SimpleHeaderTable {
|
|||
elements = newElements;
|
||||
tail = 0;
|
||||
head = size;
|
||||
this.capacity = newCapacity;
|
||||
this.capacity = capacity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ final class StringReader {
|
|||
private static final int DONE = 4;
|
||||
|
||||
private final IntegerReader intReader = new IntegerReader();
|
||||
private final Huffman.Reader huffmanReader = new Huffman.Reader();
|
||||
private final Huffman.Reader huffmanReader = new QuickHuffman.Reader();
|
||||
private final ISO_8859_1.Reader plainReader = new ISO_8859_1.Reader();
|
||||
|
||||
private int state = NEW;
|
||||
|
|
|
@ -52,7 +52,7 @@ final class StringWriter {
|
|||
private static final int DONE = 4;
|
||||
|
||||
private final IntegerWriter intWriter = new IntegerWriter();
|
||||
private final Huffman.Writer huffmanWriter = new Huffman.Writer();
|
||||
private final Huffman.Writer huffmanWriter = new QuickHuffman.Writer();
|
||||
private final ISO_8859_1.Writer plainWriter = new ISO_8859_1.Writer();
|
||||
|
||||
private int state = NEW;
|
||||
|
@ -76,7 +76,7 @@ final class StringWriter {
|
|||
intWriter.configure(end - start, 7, 0b0000_0000);
|
||||
} else {
|
||||
huffmanWriter.from(input, start, end);
|
||||
intWriter.configure(Huffman.INSTANCE.lengthOf(input, start, end),
|
||||
intWriter.configure(huffmanWriter.lengthOf(input, start, end),
|
||||
7, 0b1000_0000);
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue