2015-01-20 01:48:11 +01:00
|
|
|
/*
|
|
|
|
* Copyright 2014 The Netty Project
|
|
|
|
*
|
|
|
|
* The Netty Project licenses this file to you under the Apache License, version 2.0 (the
|
|
|
|
* "License"); you may not use this file except in compliance with the License. You may obtain a
|
|
|
|
* copy of the License at:
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
|
|
|
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
|
|
|
* or implied. See the License for the specific language governing permissions and limitations under
|
|
|
|
* the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package io.netty.handler.codec.http2;
|
|
|
|
|
|
|
|
import io.netty.buffer.ByteBuf;
|
2015-05-11 21:10:23 +02:00
|
|
|
import io.netty.buffer.ByteBufUtil;
|
2015-01-20 01:48:11 +01:00
|
|
|
import io.netty.buffer.Unpooled;
|
2015-01-31 04:54:35 +01:00
|
|
|
import io.netty.channel.Channel;
|
2015-01-20 01:48:11 +01:00
|
|
|
import io.netty.channel.ChannelHandlerContext;
|
|
|
|
import io.netty.channel.ChannelPromise;
|
2015-01-31 04:54:35 +01:00
|
|
|
import io.netty.channel.DefaultChannelPromise;
|
2015-06-01 09:05:00 +02:00
|
|
|
import io.netty.handler.ssl.ApplicationProtocolNames;
|
2015-09-11 18:56:00 +02:00
|
|
|
import io.netty.util.AsciiString;
|
2015-01-31 04:54:35 +01:00
|
|
|
import io.netty.util.concurrent.EventExecutor;
|
2016-04-12 14:22:41 +02:00
|
|
|
import io.netty.util.internal.UnstableApi;
|
2015-01-20 01:48:11 +01:00
|
|
|
|
2015-09-11 18:12:22 +02:00
|
|
|
import static io.netty.buffer.Unpooled.directBuffer;
|
|
|
|
import static io.netty.buffer.Unpooled.unreleasableBuffer;
|
2016-09-16 15:57:33 +02:00
|
|
|
import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
|
2017-01-14 02:09:44 +01:00
|
|
|
import static io.netty.handler.codec.http2.Http2Exception.connectionError;
|
2016-11-23 20:32:41 +01:00
|
|
|
import static io.netty.handler.codec.http2.Http2Exception.headerListSizeError;
|
2015-09-11 18:12:22 +02:00
|
|
|
import static io.netty.util.CharsetUtil.UTF_8;
|
2015-12-18 00:28:30 +01:00
|
|
|
import static java.lang.Math.max;
|
|
|
|
import static java.lang.Math.min;
|
2015-09-11 18:12:22 +02:00
|
|
|
|
2015-01-20 01:48:11 +01:00
|
|
|
/**
|
|
|
|
* Constants and utility method used for encoding/decoding HTTP2 frames.
|
|
|
|
*/
|
2016-04-12 14:22:41 +02:00
|
|
|
@UnstableApi
|
2015-01-20 01:48:11 +01:00
|
|
|
public final class Http2CodecUtil {
|
|
|
|
public static final int CONNECTION_STREAM_ID = 0;
|
|
|
|
public static final int HTTP_UPGRADE_STREAM_ID = 1;
|
2015-09-11 18:56:00 +02:00
|
|
|
public static final CharSequence HTTP_UPGRADE_SETTINGS_HEADER = new AsciiString("HTTP2-Settings");
|
|
|
|
public static final CharSequence HTTP_UPGRADE_PROTOCOL_NAME = "h2c";
|
|
|
|
public static final CharSequence TLS_UPGRADE_PROTOCOL_NAME = ApplicationProtocolNames.HTTP_2;
|
2015-01-20 01:48:11 +01:00
|
|
|
|
|
|
|
public static final int PING_FRAME_PAYLOAD_LENGTH = 8;
|
|
|
|
public static final short MAX_UNSIGNED_BYTE = 0xFF;
|
2016-06-23 14:09:23 +02:00
|
|
|
/**
|
|
|
|
* The maximum number of padding bytes. That is the 255 padding bytes appended to the end of a frame and the 1 byte
|
|
|
|
* pad length field.
|
|
|
|
*/
|
|
|
|
public static final int MAX_PADDING = 256;
|
2015-01-20 01:48:11 +01:00
|
|
|
public static final long MAX_UNSIGNED_INT = 0xFFFFFFFFL;
|
|
|
|
public static final int FRAME_HEADER_LENGTH = 9;
|
|
|
|
public static final int SETTING_ENTRY_LENGTH = 6;
|
|
|
|
public static final int PRIORITY_ENTRY_LENGTH = 5;
|
|
|
|
public static final int INT_FIELD_LENGTH = 4;
|
|
|
|
public static final short MAX_WEIGHT = 256;
|
|
|
|
public static final short MIN_WEIGHT = 1;
|
|
|
|
|
2016-04-13 15:25:15 +02:00
|
|
|
private static final ByteBuf CONNECTION_PREFACE =
|
|
|
|
unreleasableBuffer(directBuffer(24).writeBytes("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".getBytes(UTF_8)))
|
|
|
|
.asReadOnly();
|
|
|
|
private static final ByteBuf EMPTY_PING =
|
|
|
|
unreleasableBuffer(directBuffer(PING_FRAME_PAYLOAD_LENGTH).writeZero(PING_FRAME_PAYLOAD_LENGTH))
|
|
|
|
.asReadOnly();
|
2015-09-11 18:12:22 +02:00
|
|
|
|
2015-01-31 04:54:35 +01:00
|
|
|
private static final int MAX_PADDING_LENGTH_LENGTH = 1;
|
|
|
|
public static final int DATA_FRAME_HEADER_LENGTH = FRAME_HEADER_LENGTH + MAX_PADDING_LENGTH_LENGTH;
|
|
|
|
public static final int HEADERS_FRAME_HEADER_LENGTH =
|
|
|
|
FRAME_HEADER_LENGTH + MAX_PADDING_LENGTH_LENGTH + INT_FIELD_LENGTH + 1;
|
|
|
|
public static final int PRIORITY_FRAME_LENGTH = FRAME_HEADER_LENGTH + PRIORITY_ENTRY_LENGTH;
|
|
|
|
public static final int RST_STREAM_FRAME_LENGTH = FRAME_HEADER_LENGTH + INT_FIELD_LENGTH;
|
|
|
|
public static final int PUSH_PROMISE_FRAME_HEADER_LENGTH =
|
|
|
|
FRAME_HEADER_LENGTH + MAX_PADDING_LENGTH_LENGTH + INT_FIELD_LENGTH;
|
|
|
|
public static final int GO_AWAY_FRAME_HEADER_LENGTH = FRAME_HEADER_LENGTH + 2 * INT_FIELD_LENGTH;
|
|
|
|
public static final int WINDOW_UPDATE_FRAME_LENGTH = FRAME_HEADER_LENGTH + INT_FIELD_LENGTH;
|
|
|
|
public static final int CONTINUATION_FRAME_HEADER_LENGTH = FRAME_HEADER_LENGTH + MAX_PADDING_LENGTH_LENGTH;
|
|
|
|
|
2015-04-10 18:13:32 +02:00
|
|
|
public static final char SETTINGS_HEADER_TABLE_SIZE = 1;
|
|
|
|
public static final char SETTINGS_ENABLE_PUSH = 2;
|
|
|
|
public static final char SETTINGS_MAX_CONCURRENT_STREAMS = 3;
|
|
|
|
public static final char SETTINGS_INITIAL_WINDOW_SIZE = 4;
|
|
|
|
public static final char SETTINGS_MAX_FRAME_SIZE = 5;
|
|
|
|
public static final char SETTINGS_MAX_HEADER_LIST_SIZE = 6;
|
2015-04-01 16:51:11 +02:00
|
|
|
public static final int NUM_STANDARD_SETTINGS = 6;
|
2015-01-20 01:48:11 +01:00
|
|
|
|
2016-09-05 14:58:08 +02:00
|
|
|
public static final long MAX_HEADER_TABLE_SIZE = MAX_UNSIGNED_INT;
|
2015-01-20 01:48:11 +01:00
|
|
|
public static final long MAX_CONCURRENT_STREAMS = MAX_UNSIGNED_INT;
|
|
|
|
public static final int MAX_INITIAL_WINDOW_SIZE = Integer.MAX_VALUE;
|
|
|
|
public static final int MAX_FRAME_SIZE_LOWER_BOUND = 0x4000;
|
|
|
|
public static final int MAX_FRAME_SIZE_UPPER_BOUND = 0xFFFFFF;
|
2016-09-16 15:57:33 +02:00
|
|
|
public static final long MAX_HEADER_LIST_SIZE = MAX_UNSIGNED_INT;
|
2015-01-20 01:48:11 +01:00
|
|
|
|
|
|
|
public static final long MIN_HEADER_TABLE_SIZE = 0;
|
|
|
|
public static final long MIN_CONCURRENT_STREAMS = 0;
|
|
|
|
public static final int MIN_INITIAL_WINDOW_SIZE = 0;
|
|
|
|
public static final long MIN_HEADER_LIST_SIZE = 0;
|
|
|
|
|
|
|
|
public static final int DEFAULT_WINDOW_SIZE = 65535;
|
|
|
|
public static final short DEFAULT_PRIORITY_WEIGHT = 16;
|
|
|
|
public static final int DEFAULT_HEADER_TABLE_SIZE = 4096;
|
2017-01-14 02:09:44 +01:00
|
|
|
/**
|
|
|
|
* <a href="https://tools.ietf.org/html/rfc7540#section-6.5.2">The initial value of this setting is unlimited</a>.
|
|
|
|
* However in practice we don't want to allow our peers to use unlimited memory by default. So we take advantage
|
|
|
|
* of the <q>For any given request, a lower limit than what is advertised MAY be enforced.</q> loophole.
|
|
|
|
*/
|
|
|
|
public static final long DEFAULT_HEADER_LIST_SIZE = 8192;
|
2015-01-20 01:48:11 +01:00
|
|
|
public static final int DEFAULT_MAX_FRAME_SIZE = MAX_FRAME_SIZE_LOWER_BOUND;
|
|
|
|
|
2017-01-21 00:14:42 +01:00
|
|
|
/**
|
|
|
|
* Calculate the threshold in bytes which should trigger a {@code GO_AWAY} if a set of headers exceeds this amount.
|
|
|
|
* @param maxHeaderListSize
|
|
|
|
* <a href="https://tools.ietf.org/html/rfc7540#section-6.5.2">SETTINGS_MAX_HEADER_LIST_SIZE</a> for the local
|
|
|
|
* endpoint.
|
|
|
|
* @return the threshold in bytes which should trigger a {@code GO_AWAY} if a set of headers exceeds this amount.
|
|
|
|
*/
|
|
|
|
public static long calculateMaxHeaderListSizeGoAway(long maxHeaderListSize) {
|
|
|
|
// This is equivalent to `maxHeaderListSize * 1.25` but we avoid floating point multiplication.
|
|
|
|
return maxHeaderListSize + (maxHeaderListSize >>> 2);
|
|
|
|
}
|
|
|
|
|
2016-08-16 15:22:39 +02:00
|
|
|
/**
|
|
|
|
* Returns {@code true} if the stream is an outbound stream.
|
|
|
|
*
|
|
|
|
* @param server {@code true} if the endpoint is a server, {@code false} otherwise.
|
|
|
|
* @param streamId the stream identifier
|
|
|
|
*/
|
|
|
|
public static boolean isOutboundStream(boolean server, int streamId) {
|
|
|
|
boolean even = (streamId & 1) == 0;
|
|
|
|
return streamId > 0 && server == even;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns true if the {@code streamId} is a valid HTTP/2 stream identifier.
|
|
|
|
*/
|
|
|
|
public static boolean isStreamIdValid(int streamId) {
|
|
|
|
return streamId >= 0;
|
|
|
|
}
|
|
|
|
|
2015-06-03 05:15:59 +02:00
|
|
|
/**
|
|
|
|
* The assumed minimum value for {@code SETTINGS_MAX_CONCURRENT_STREAMS} as
|
|
|
|
* recommended by the HTTP/2 spec.
|
|
|
|
*/
|
|
|
|
public static final int SMALLEST_MAX_CONCURRENT_STREAMS = 100;
|
|
|
|
|
2015-01-20 01:48:11 +01:00
|
|
|
/**
|
|
|
|
* Indicates whether or not the given value for max frame size falls within the valid range.
|
|
|
|
*/
|
|
|
|
public static boolean isMaxFrameSizeValid(int maxFrameSize) {
|
|
|
|
return maxFrameSize >= MAX_FRAME_SIZE_LOWER_BOUND && maxFrameSize <= MAX_FRAME_SIZE_UPPER_BOUND;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a buffer containing the the {@link #CONNECTION_PREFACE}.
|
|
|
|
*/
|
|
|
|
public static ByteBuf connectionPrefaceBuf() {
|
|
|
|
// Return a duplicate so that modifications to the reader index will not affect the original buffer.
|
2016-04-14 10:31:48 +02:00
|
|
|
return CONNECTION_PREFACE.retainedDuplicate();
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a buffer filled with all zeros that is the appropriate length for a PING frame.
|
|
|
|
*/
|
|
|
|
public static ByteBuf emptyPingBuf() {
|
|
|
|
// Return a duplicate so that modifications to the reader index will not affect the original buffer.
|
2016-04-14 10:31:48 +02:00
|
|
|
return EMPTY_PING.retainedDuplicate();
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Iteratively looks through the causaility chain for the given exception and returns the first
|
|
|
|
* {@link Http2Exception} or {@code null} if none.
|
|
|
|
*/
|
|
|
|
public static Http2Exception getEmbeddedHttp2Exception(Throwable cause) {
|
|
|
|
while (cause != null) {
|
|
|
|
if (cause instanceof Http2Exception) {
|
|
|
|
return (Http2Exception) cause;
|
|
|
|
}
|
|
|
|
cause = cause.getCause();
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a buffer containing the error message from the given exception. If the cause is
|
|
|
|
* {@code null} returns an empty buffer.
|
|
|
|
*/
|
|
|
|
public static ByteBuf toByteBuf(ChannelHandlerContext ctx, Throwable cause) {
|
|
|
|
if (cause == null || cause.getMessage() == null) {
|
|
|
|
return Unpooled.EMPTY_BUFFER;
|
|
|
|
}
|
|
|
|
|
2016-03-05 02:08:40 +01:00
|
|
|
return ByteBufUtil.writeUtf8(ctx.alloc(), cause.getMessage());
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Reads a big-endian (31-bit) integer from the buffer.
|
|
|
|
*/
|
|
|
|
public static int readUnsignedInt(ByteBuf buf) {
|
|
|
|
return (buf.readByte() & 0x7F) << 24 | (buf.readByte() & 0xFF) << 16
|
|
|
|
| (buf.readByte() & 0xFF) << 8 | buf.readByte() & 0xFF;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Writes a big-endian (32-bit) unsigned integer to the buffer.
|
|
|
|
*/
|
|
|
|
public static void writeUnsignedInt(long value, ByteBuf out) {
|
|
|
|
out.writeByte((int) (value >> 24 & 0xFF));
|
|
|
|
out.writeByte((int) (value >> 16 & 0xFF));
|
|
|
|
out.writeByte((int) (value >> 8 & 0xFF));
|
|
|
|
out.writeByte((int) (value & 0xFF));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Writes a big-endian (16-bit) unsigned integer to the buffer.
|
|
|
|
*/
|
|
|
|
public static void writeUnsignedShort(int value, ByteBuf out) {
|
|
|
|
out.writeByte(value >> 8 & 0xFF);
|
|
|
|
out.writeByte(value & 0xFF);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Writes an HTTP/2 frame header to the output buffer.
|
|
|
|
*/
|
|
|
|
public static void writeFrameHeader(ByteBuf out, int payloadLength, byte type,
|
|
|
|
Http2Flags flags, int streamId) {
|
|
|
|
out.ensureWritable(FRAME_HEADER_LENGTH + payloadLength);
|
2015-01-31 04:54:35 +01:00
|
|
|
writeFrameHeaderInternal(out, payloadLength, type, flags, streamId);
|
|
|
|
}
|
|
|
|
|
2015-12-18 00:28:30 +01:00
|
|
|
/**
|
|
|
|
* Calculate the amount of bytes that can be sent by {@code state}. The lower bound is {@code 0}.
|
|
|
|
*/
|
|
|
|
public static int streamableBytes(StreamByteDistributor.StreamState state) {
|
|
|
|
return max(0, min(state.pendingBytes(), state.windowSize()));
|
|
|
|
}
|
|
|
|
|
2017-01-14 02:09:44 +01:00
|
|
|
/**
|
|
|
|
* Results in a RST_STREAM being sent for {@code streamId} due to violating
|
|
|
|
* <a href="https://tools.ietf.org/html/rfc7540#section-6.5.2">SETTINGS_MAX_HEADER_LIST_SIZE</a>.
|
|
|
|
* @param streamId The stream ID that was being processed when the exceptional condition occurred.
|
|
|
|
* @param maxHeaderListSize The max allowed size for a list of headers in bytes which was exceeded.
|
|
|
|
* @param onDecode {@code true} if the exception was encountered during decoder. {@code false} for encode.
|
|
|
|
* @throws Http2Exception a stream error.
|
|
|
|
*/
|
2016-11-23 20:32:41 +01:00
|
|
|
public static void headerListSizeExceeded(int streamId, long maxHeaderListSize,
|
|
|
|
boolean onDecode) throws Http2Exception {
|
|
|
|
throw headerListSizeError(streamId, PROTOCOL_ERROR, onDecode, "Header size exceeded max " +
|
|
|
|
"allowed size (%d)", maxHeaderListSize);
|
2016-09-16 15:57:33 +02:00
|
|
|
}
|
|
|
|
|
2017-01-14 02:09:44 +01:00
|
|
|
/**
|
|
|
|
* Results in a GO_AWAY being sent due to violating
|
|
|
|
* <a href="https://tools.ietf.org/html/rfc7540#section-6.5.2">SETTINGS_MAX_HEADER_LIST_SIZE</a> in an unrecoverable
|
|
|
|
* manner.
|
|
|
|
* @param maxHeaderListSize The max allowed size for a list of headers in bytes which was exceeded.
|
|
|
|
* @throws Http2Exception a connection error.
|
|
|
|
*/
|
|
|
|
public static void headerListSizeExceeded(long maxHeaderListSize) throws Http2Exception {
|
|
|
|
throw connectionError(PROTOCOL_ERROR, "Header size exceeded max " +
|
|
|
|
"allowed size (%d)", maxHeaderListSize);
|
|
|
|
}
|
|
|
|
|
2015-01-31 04:54:35 +01:00
|
|
|
static void writeFrameHeaderInternal(ByteBuf out, int payloadLength, byte type,
|
|
|
|
Http2Flags flags, int streamId) {
|
2015-01-20 01:48:11 +01:00
|
|
|
out.writeMedium(payloadLength);
|
|
|
|
out.writeByte(type);
|
|
|
|
out.writeByte(flags.value());
|
|
|
|
out.writeInt(streamId);
|
|
|
|
}
|
|
|
|
|
2015-01-31 04:54:35 +01:00
|
|
|
/**
|
|
|
|
* Provides the ability to associate the outcome of multiple {@link ChannelPromise}
|
|
|
|
* objects into a single {@link ChannelPromise} object.
|
|
|
|
*/
|
2016-03-07 19:05:22 +01:00
|
|
|
static final class SimpleChannelPromiseAggregator extends DefaultChannelPromise {
|
2015-01-31 04:54:35 +01:00
|
|
|
private final ChannelPromise promise;
|
|
|
|
private int expectedCount;
|
2016-03-07 19:05:22 +01:00
|
|
|
private int doneCount;
|
|
|
|
private Throwable lastFailure;
|
2015-01-31 04:54:35 +01:00
|
|
|
private boolean doneAllocating;
|
|
|
|
|
|
|
|
SimpleChannelPromiseAggregator(ChannelPromise promise, Channel c, EventExecutor e) {
|
|
|
|
super(c, e);
|
2016-03-07 19:05:22 +01:00
|
|
|
assert promise != null && !promise.isDone();
|
2015-01-31 04:54:35 +01:00
|
|
|
this.promise = promise;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Allocate a new promise which will be used to aggregate the overall success of this promise aggregator.
|
|
|
|
* @return A new promise which will be aggregated.
|
|
|
|
* {@code null} if {@link #doneAllocatingPromises()} was previously called.
|
|
|
|
*/
|
|
|
|
public ChannelPromise newPromise() {
|
2016-03-07 19:05:22 +01:00
|
|
|
assert !doneAllocating : "Done allocating. No more promises can be allocated.";
|
2015-01-31 04:54:35 +01:00
|
|
|
++expectedCount;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Signify that no more {@link #newPromise()} allocations will be made.
|
|
|
|
* The aggregation can not be successful until this method is called.
|
|
|
|
* @return The promise that is the aggregation of all promises allocated with {@link #newPromise()}.
|
|
|
|
*/
|
|
|
|
public ChannelPromise doneAllocatingPromises() {
|
|
|
|
if (!doneAllocating) {
|
|
|
|
doneAllocating = true;
|
2016-03-07 19:05:22 +01:00
|
|
|
if (doneCount == expectedCount || expectedCount == 0) {
|
|
|
|
return setPromise();
|
2015-01-31 04:54:35 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean tryFailure(Throwable cause) {
|
2015-08-29 01:29:47 +02:00
|
|
|
if (allowFailure()) {
|
2016-03-07 19:05:22 +01:00
|
|
|
++doneCount;
|
|
|
|
lastFailure = cause;
|
|
|
|
if (allPromisesDone()) {
|
|
|
|
return tryPromise();
|
2015-01-31 04:54:35 +01:00
|
|
|
}
|
|
|
|
// TODO: We break the interface a bit here.
|
|
|
|
// Multiple failure events can be processed without issue because this is an aggregation.
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Fail this object if it has not already been failed.
|
|
|
|
* <p>
|
|
|
|
* This method will NOT throw an {@link IllegalStateException} if called multiple times
|
|
|
|
* because that may be expected.
|
|
|
|
*/
|
|
|
|
@Override
|
|
|
|
public ChannelPromise setFailure(Throwable cause) {
|
2015-08-29 01:29:47 +02:00
|
|
|
if (allowFailure()) {
|
2016-03-07 19:05:22 +01:00
|
|
|
++doneCount;
|
|
|
|
lastFailure = cause;
|
|
|
|
if (allPromisesDone()) {
|
|
|
|
return setPromise();
|
2015-01-31 04:54:35 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public ChannelPromise setSuccess(Void result) {
|
2015-05-05 00:18:12 +02:00
|
|
|
if (awaitingPromises()) {
|
2016-03-07 19:05:22 +01:00
|
|
|
++doneCount;
|
|
|
|
if (allPromisesDone()) {
|
|
|
|
setPromise();
|
2015-01-31 04:54:35 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean trySuccess(Void result) {
|
2015-05-05 00:18:12 +02:00
|
|
|
if (awaitingPromises()) {
|
2016-03-07 19:05:22 +01:00
|
|
|
++doneCount;
|
|
|
|
if (allPromisesDone()) {
|
|
|
|
return tryPromise();
|
2015-01-31 04:54:35 +01:00
|
|
|
}
|
|
|
|
// TODO: We break the interface a bit here.
|
|
|
|
// Multiple success events can be processed without issue because this is an aggregation.
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2016-03-07 19:05:22 +01:00
|
|
|
|
|
|
|
private boolean allowFailure() {
|
|
|
|
return awaitingPromises() || expectedCount == 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
private boolean awaitingPromises() {
|
|
|
|
return doneCount < expectedCount;
|
|
|
|
}
|
|
|
|
|
|
|
|
private boolean allPromisesDone() {
|
|
|
|
return doneCount == expectedCount && doneAllocating;
|
|
|
|
}
|
|
|
|
|
|
|
|
private ChannelPromise setPromise() {
|
|
|
|
if (lastFailure == null) {
|
|
|
|
promise.setSuccess();
|
|
|
|
return super.setSuccess(null);
|
|
|
|
} else {
|
|
|
|
promise.setFailure(lastFailure);
|
|
|
|
return super.setFailure(lastFailure);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private boolean tryPromise() {
|
|
|
|
if (lastFailure == null) {
|
|
|
|
promise.trySuccess();
|
|
|
|
return super.trySuccess(null);
|
|
|
|
} else {
|
|
|
|
promise.tryFailure(lastFailure);
|
|
|
|
return super.tryFailure(lastFailure);
|
|
|
|
}
|
|
|
|
}
|
2015-01-31 04:54:35 +01:00
|
|
|
}
|
|
|
|
|
2016-06-23 14:09:23 +02:00
|
|
|
public static void verifyPadding(int padding) {
|
|
|
|
if (padding < 0 || padding > MAX_PADDING) {
|
|
|
|
throw new IllegalArgumentException(String.format("Invalid padding '%d'. Padding must be between 0 and " +
|
|
|
|
"%d (inclusive).", padding, MAX_PADDING));
|
|
|
|
}
|
|
|
|
}
|
2015-01-20 01:48:11 +01:00
|
|
|
private Http2CodecUtil() { }
|
|
|
|
}
|