/*
 * 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;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.HttpStatusClass;
import io.netty.handler.codec.http2.Http2Connection.Endpoint;
import io.netty.util.internal.UnstableApi;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;

import java.util.List;

import static io.netty.handler.codec.http.HttpStatusClass.INFORMATIONAL;
import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_PRIORITY_WEIGHT;
import static io.netty.handler.codec.http2.Http2Error.INTERNAL_ERROR;
import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
import static io.netty.handler.codec.http2.Http2Error.STREAM_CLOSED;
import static io.netty.handler.codec.http2.Http2Exception.connectionError;
import static io.netty.handler.codec.http2.Http2Exception.streamError;
import static io.netty.handler.codec.http2.Http2PromisedRequestVerifier.ALWAYS_VERIFY;
import static io.netty.handler.codec.http2.Http2Stream.State.CLOSED;
import static io.netty.handler.codec.http2.Http2Stream.State.HALF_CLOSED_REMOTE;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import static java.lang.Integer.MAX_VALUE;
import static java.lang.Math.min;

Provides the default implementation for processing inbound frame events and delegates to a Http2FrameListener

This class will read HTTP/2 frames and delegate the events to a Http2FrameListener

This interface enforces inbound flow control functionality through Http2LocalFlowController

/** * Provides the default implementation for processing inbound frame events and delegates to a * {@link Http2FrameListener} * <p> * This class will read HTTP/2 frames and delegate the events to a {@link Http2FrameListener} * <p> * This interface enforces inbound flow control functionality through * {@link Http2LocalFlowController} */
@UnstableApi public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder { private static final InternalLogger logger = InternalLoggerFactory.getInstance(DefaultHttp2ConnectionDecoder.class); private Http2FrameListener internalFrameListener = new PrefaceFrameListener(); private final Http2Connection connection; private Http2LifecycleManager lifecycleManager; private final Http2ConnectionEncoder encoder; private final Http2FrameReader frameReader; private Http2FrameListener listener; private final Http2PromisedRequestVerifier requestVerifier; public DefaultHttp2ConnectionDecoder(Http2Connection connection, Http2ConnectionEncoder encoder, Http2FrameReader frameReader) { this(connection, encoder, frameReader, ALWAYS_VERIFY); } public DefaultHttp2ConnectionDecoder(Http2Connection connection, Http2ConnectionEncoder encoder, Http2FrameReader frameReader, Http2PromisedRequestVerifier requestVerifier) { this.connection = checkNotNull(connection, "connection"); this.frameReader = checkNotNull(frameReader, "frameReader"); this.encoder = checkNotNull(encoder, "encoder"); this.requestVerifier = checkNotNull(requestVerifier, "requestVerifier"); if (connection.local().flowController() == null) { connection.local().flowController(new DefaultHttp2LocalFlowController(connection)); } connection.local().flowController().frameWriter(encoder.frameWriter()); } @Override public void lifecycleManager(Http2LifecycleManager lifecycleManager) { this.lifecycleManager = checkNotNull(lifecycleManager, "lifecycleManager"); } @Override public Http2Connection connection() { return connection; } @Override public final Http2LocalFlowController flowController() { return connection.local().flowController(); } @Override public void frameListener(Http2FrameListener listener) { this.listener = checkNotNull(listener, "listener"); } @Override public Http2FrameListener frameListener() { return listener; } // Visible for testing Http2FrameListener internalFrameListener() { return internalFrameListener; } @Override public boolean prefaceReceived() { return FrameReadListener.class == internalFrameListener.getClass(); } @Override public void decodeFrame(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Http2Exception { frameReader.readFrame(ctx, in, internalFrameListener); } @Override public Http2Settings localSettings() { Http2Settings settings = new Http2Settings(); Http2FrameReader.Configuration config = frameReader.configuration(); Http2HeadersDecoder.Configuration headersConfig = config.headersConfiguration(); Http2FrameSizePolicy frameSizePolicy = config.frameSizePolicy(); settings.initialWindowSize(flowController().initialWindowSize()); settings.maxConcurrentStreams(connection.remote().maxActiveStreams()); settings.headerTableSize(headersConfig.maxHeaderTableSize()); settings.maxFrameSize(frameSizePolicy.maxFrameSize()); settings.maxHeaderListSize(headersConfig.maxHeaderListSize()); if (!connection.isServer()) { // Only set the pushEnabled flag if this is a client endpoint. settings.pushEnabled(connection.local().allowPushTo()); } return settings; } @Override public void close() { frameReader.close(); }
Calculate the threshold in bytes which should trigger a GO_AWAY if a set of headers exceeds this amount.
Params:
Returns:the threshold in bytes which should trigger a GO_AWAY if a set of headers exceeds this amount.
/** * 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. */
protected long calculateMaxHeaderListSizeGoAway(long maxHeaderListSize) { return Http2CodecUtil.calculateMaxHeaderListSizeGoAway(maxHeaderListSize); } private int unconsumedBytes(Http2Stream stream) { return flowController().unconsumedBytes(stream); } void onGoAwayRead0(ChannelHandlerContext ctx, int lastStreamId, long errorCode, ByteBuf debugData) throws Http2Exception { if (connection.goAwayReceived() && connection.local().lastStreamKnownByPeer() < lastStreamId) { throw connectionError(PROTOCOL_ERROR, "lastStreamId MUST NOT increase. Current value: %d new value: %d", connection.local().lastStreamKnownByPeer(), lastStreamId); } listener.onGoAwayRead(ctx, lastStreamId, errorCode, debugData); connection.goAwayReceived(lastStreamId, errorCode, debugData); } void onUnknownFrame0(ChannelHandlerContext ctx, byte frameType, int streamId, Http2Flags flags, ByteBuf payload) throws Http2Exception { listener.onUnknownFrame(ctx, frameType, streamId, flags, payload); }
Handles all inbound frames from the network.
/** * Handles all inbound frames from the network. */
private final class FrameReadListener implements Http2FrameListener { @Override public int onDataRead(final ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, boolean endOfStream) throws Http2Exception { Http2Stream stream = connection.stream(streamId); Http2LocalFlowController flowController = flowController(); int bytesToReturn = data.readableBytes() + padding; final boolean shouldIgnore; try { shouldIgnore = shouldIgnoreHeadersOrDataFrame(ctx, streamId, stream, "DATA"); } catch (Http2Exception e) { // Ignoring this frame. We still need to count the frame towards the connection flow control // window, but we immediately mark all bytes as consumed. flowController.receiveFlowControlledFrame(stream, data, padding, endOfStream); flowController.consumeBytes(stream, bytesToReturn); throw e; } catch (Throwable t) { throw connectionError(INTERNAL_ERROR, t, "Unhandled error on data stream id %d", streamId); } if (shouldIgnore) { // Ignoring this frame. We still need to count the frame towards the connection flow control // window, but we immediately mark all bytes as consumed. flowController.receiveFlowControlledFrame(stream, data, padding, endOfStream); flowController.consumeBytes(stream, bytesToReturn); // Verify that the stream may have existed after we apply flow control. verifyStreamMayHaveExisted(streamId); // All bytes have been consumed. return bytesToReturn; } Http2Exception error = null; switch (stream.state()) { case OPEN: case HALF_CLOSED_LOCAL: break; case HALF_CLOSED_REMOTE: case CLOSED: error = streamError(stream.id(), STREAM_CLOSED, "Stream %d in unexpected state: %s", stream.id(), stream.state()); break; default: error = streamError(stream.id(), PROTOCOL_ERROR, "Stream %d in unexpected state: %s", stream.id(), stream.state()); break; } int unconsumedBytes = unconsumedBytes(stream); try { flowController.receiveFlowControlledFrame(stream, data, padding, endOfStream); // Update the unconsumed bytes after flow control is applied. unconsumedBytes = unconsumedBytes(stream); // If the stream is in an invalid state to receive the frame, throw the error. if (error != null) { throw error; } // Call back the application and retrieve the number of bytes that have been // immediately processed. bytesToReturn = listener.onDataRead(ctx, streamId, data, padding, endOfStream); return bytesToReturn; } catch (Http2Exception e) { // If an exception happened during delivery, the listener may have returned part // of the bytes before the error occurred. If that's the case, subtract that from // the total processed bytes so that we don't return too many bytes. int delta = unconsumedBytes - unconsumedBytes(stream); bytesToReturn -= delta; throw e; } catch (RuntimeException e) { // If an exception happened during delivery, the listener may have returned part // of the bytes before the error occurred. If that's the case, subtract that from // the total processed bytes so that we don't return too many bytes. int delta = unconsumedBytes - unconsumedBytes(stream); bytesToReturn -= delta; throw e; } finally { // If appropriate, return the processed bytes to the flow controller. flowController.consumeBytes(stream, bytesToReturn); if (endOfStream) { lifecycleManager.closeStreamRemote(stream, ctx.newSucceededFuture()); } } } @Override public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int padding, boolean endOfStream) throws Http2Exception { onHeadersRead(ctx, streamId, headers, 0, DEFAULT_PRIORITY_WEIGHT, false, padding, endOfStream); } @Override public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int streamDependency, short weight, boolean exclusive, int padding, boolean endOfStream) throws Http2Exception { Http2Stream stream = connection.stream(streamId); boolean allowHalfClosedRemote = false; if (stream == null && !connection.streamMayHaveExisted(streamId)) { stream = connection.remote().createStream(streamId, endOfStream); // Allow the state to be HALF_CLOSE_REMOTE if we're creating it in that state. allowHalfClosedRemote = stream.state() == HALF_CLOSED_REMOTE; } if (shouldIgnoreHeadersOrDataFrame(ctx, streamId, stream, "HEADERS")) { return; } boolean isInformational = !connection.isServer() && HttpStatusClass.valueOf(headers.status()) == INFORMATIONAL; if ((isInformational || !endOfStream) && stream.isHeadersReceived() || stream.isTrailersReceived()) { throw streamError(streamId, PROTOCOL_ERROR, "Stream %d received too many headers EOS: %s state: %s", streamId, endOfStream, stream.state()); } switch (stream.state()) { case RESERVED_REMOTE: stream.open(endOfStream); break; case OPEN: case HALF_CLOSED_LOCAL: // Allowed to receive headers in these states. break; case HALF_CLOSED_REMOTE: if (!allowHalfClosedRemote) { throw streamError(stream.id(), STREAM_CLOSED, "Stream %d in unexpected state: %s", stream.id(), stream.state()); } break; case CLOSED: throw streamError(stream.id(), STREAM_CLOSED, "Stream %d in unexpected state: %s", stream.id(), stream.state()); default: // Connection error. throw connectionError(PROTOCOL_ERROR, "Stream %d in unexpected state: %s", stream.id(), stream.state()); } stream.headersReceived(isInformational); encoder.flowController().updateDependencyTree(streamId, streamDependency, weight, exclusive); listener.onHeadersRead(ctx, streamId, headers, streamDependency, weight, exclusive, padding, endOfStream); // If the headers completes this stream, close it. if (endOfStream) { lifecycleManager.closeStreamRemote(stream, ctx.newSucceededFuture()); } } @Override public void onPriorityRead(ChannelHandlerContext ctx, int streamId, int streamDependency, short weight, boolean exclusive) throws Http2Exception { encoder.flowController().updateDependencyTree(streamId, streamDependency, weight, exclusive); listener.onPriorityRead(ctx, streamId, streamDependency, weight, exclusive); } @Override public void onRstStreamRead(ChannelHandlerContext ctx, int streamId, long errorCode) throws Http2Exception { Http2Stream stream = connection.stream(streamId); if (stream == null) { verifyStreamMayHaveExisted(streamId); return; } switch(stream.state()) { case IDLE: throw connectionError(PROTOCOL_ERROR, "RST_STREAM received for IDLE stream %d", streamId); case CLOSED: return; // RST_STREAM frames must be ignored for closed streams. default: break; } listener.onRstStreamRead(ctx, streamId, errorCode); lifecycleManager.closeStream(stream, ctx.newSucceededFuture()); } @Override public void onSettingsAckRead(ChannelHandlerContext ctx) throws Http2Exception { // Apply oldest outstanding local settings here. This is a synchronization point between endpoints. Http2Settings settings = encoder.pollSentSettings(); if (settings != null) { applyLocalSettings(settings); } listener.onSettingsAckRead(ctx); }
Applies settings sent from the local endpoint.

This method is only called after the local settings have been acknowledged from the remote endpoint.

/** * Applies settings sent from the local endpoint. * <p> * This method is only called after the local settings have been acknowledged from the remote endpoint. */
private void applyLocalSettings(Http2Settings settings) throws Http2Exception { Boolean pushEnabled = settings.pushEnabled(); final Http2FrameReader.Configuration config = frameReader.configuration(); final Http2HeadersDecoder.Configuration headerConfig = config.headersConfiguration(); final Http2FrameSizePolicy frameSizePolicy = config.frameSizePolicy(); if (pushEnabled != null) { if (connection.isServer()) { throw connectionError(PROTOCOL_ERROR, "Server sending SETTINGS frame with ENABLE_PUSH specified"); } connection.local().allowPushTo(pushEnabled); } Long maxConcurrentStreams = settings.maxConcurrentStreams(); if (maxConcurrentStreams != null) { connection.remote().maxActiveStreams((int) min(maxConcurrentStreams, MAX_VALUE)); } Long headerTableSize = settings.headerTableSize(); if (headerTableSize != null) { headerConfig.maxHeaderTableSize(headerTableSize); } Long maxHeaderListSize = settings.maxHeaderListSize(); if (maxHeaderListSize != null) { headerConfig.maxHeaderListSize(maxHeaderListSize, calculateMaxHeaderListSizeGoAway(maxHeaderListSize)); } Integer maxFrameSize = settings.maxFrameSize(); if (maxFrameSize != null) { frameSizePolicy.maxFrameSize(maxFrameSize); } Integer initialWindowSize = settings.initialWindowSize(); if (initialWindowSize != null) { flowController().initialWindowSize(initialWindowSize); } } @Override public void onSettingsRead(ChannelHandlerContext ctx, Http2Settings settings) throws Http2Exception { // Acknowledge receipt of the settings. We should do this before we process the settings to ensure our // remote peer applies these settings before any subsequent frames that we may send which depend upon these // new settings. See https://github.com/netty/netty/issues/6520. encoder.writeSettingsAck(ctx, ctx.newPromise()); encoder.remoteSettings(settings); listener.onSettingsRead(ctx, settings); } @Override public void onPingRead(ChannelHandlerContext ctx, long data) throws Http2Exception { // Send an ack back to the remote client. // Need to retain the buffer here since it will be released after the write completes. encoder.writePing(ctx, true, data, ctx.newPromise()); listener.onPingRead(ctx, data); } @Override public void onPingAckRead(ChannelHandlerContext ctx, long data) throws Http2Exception { listener.onPingAckRead(ctx, data); } @Override public void onPushPromiseRead(ChannelHandlerContext ctx, int streamId, int promisedStreamId, Http2Headers headers, int padding) throws Http2Exception { // A client cannot push. if (connection().isServer()) { throw connectionError(PROTOCOL_ERROR, "A client cannot push."); } Http2Stream parentStream = connection.stream(streamId); if (shouldIgnoreHeadersOrDataFrame(ctx, streamId, parentStream, "PUSH_PROMISE")) { return; } if (parentStream == null) { throw connectionError(PROTOCOL_ERROR, "Stream %d does not exist", streamId); } switch (parentStream.state()) { case OPEN: case HALF_CLOSED_LOCAL: // Allowed to receive push promise in these states. break; default: // Connection error. throw connectionError(PROTOCOL_ERROR, "Stream %d in unexpected state for receiving push promise: %s", parentStream.id(), parentStream.state()); } if (!requestVerifier.isAuthoritative(ctx, headers)) { throw streamError(promisedStreamId, PROTOCOL_ERROR, "Promised request on stream %d for promised stream %d is not authoritative", streamId, promisedStreamId); } if (!requestVerifier.isCacheable(headers)) { throw streamError(promisedStreamId, PROTOCOL_ERROR, "Promised request on stream %d for promised stream %d is not known to be cacheable", streamId, promisedStreamId); } if (!requestVerifier.isSafe(headers)) { throw streamError(promisedStreamId, PROTOCOL_ERROR, "Promised request on stream %d for promised stream %d is not known to be safe", streamId, promisedStreamId); } // Reserve the push stream based with a priority based on the current stream's priority. connection.remote().reservePushStream(promisedStreamId, parentStream); listener.onPushPromiseRead(ctx, streamId, promisedStreamId, headers, padding); } @Override public void onGoAwayRead(ChannelHandlerContext ctx, int lastStreamId, long errorCode, ByteBuf debugData) throws Http2Exception { onGoAwayRead0(ctx, lastStreamId, errorCode, debugData); } @Override public void onWindowUpdateRead(ChannelHandlerContext ctx, int streamId, int windowSizeIncrement) throws Http2Exception { Http2Stream stream = connection.stream(streamId); if (stream == null || stream.state() == CLOSED || streamCreatedAfterGoAwaySent(streamId)) { // Ignore this frame. verifyStreamMayHaveExisted(streamId); return; } // Update the outbound flow control window. encoder.flowController().incrementWindowSize(stream, windowSizeIncrement); listener.onWindowUpdateRead(ctx, streamId, windowSizeIncrement); } @Override public void onUnknownFrame(ChannelHandlerContext ctx, byte frameType, int streamId, Http2Flags flags, ByteBuf payload) throws Http2Exception { onUnknownFrame0(ctx, frameType, streamId, flags, payload); }
Helper method to determine if a frame that has the semantics of headers or data should be ignored for the stream (which may be null) associated with streamId.
/** * Helper method to determine if a frame that has the semantics of headers or data should be ignored for the * {@code stream} (which may be {@code null}) associated with {@code streamId}. */
private boolean shouldIgnoreHeadersOrDataFrame(ChannelHandlerContext ctx, int streamId, Http2Stream stream, String frameName) throws Http2Exception { if (stream == null) { if (streamCreatedAfterGoAwaySent(streamId)) { logger.info("{} ignoring {} frame for stream {}. Stream sent after GOAWAY sent", ctx.channel(), frameName, streamId); return true; } // Its possible that this frame would result in stream ID out of order creation (PROTOCOL ERROR) and its // also possible that this frame is received on a CLOSED stream (STREAM_CLOSED after a RST_STREAM is // sent). We don't have enough information to know for sure, so we choose the lesser of the two errors. throw streamError(streamId, STREAM_CLOSED, "Received %s frame for an unknown stream %d", frameName, streamId); } else if (stream.isResetSent() || streamCreatedAfterGoAwaySent(streamId)) { if (logger.isInfoEnabled()) { logger.info("{} ignoring {} frame for stream {} {}", ctx.channel(), frameName, stream.isResetSent() ? "RST_STREAM sent." : ("Stream created after GOAWAY sent. Last known stream by peer " + connection.remote().lastStreamKnownByPeer())); } return true; } return false; }
Helper method for determining whether or not to ignore inbound frames. A stream is considered to be created after a GOAWAY is sent if the following conditions hold:

  • A GOAWAY must have been sent by the local endpoint
  • The streamId must identify a legitimate stream id for the remote endpoint to be creating
  • streamId is greater than the Last Known Stream ID which was sent by the local endpoint in the last GOAWAY frame

/** * Helper method for determining whether or not to ignore inbound frames. A stream is considered to be created * after a {@code GOAWAY} is sent if the following conditions hold: * <p/> * <ul> * <li>A {@code GOAWAY} must have been sent by the local endpoint</li> * <li>The {@code streamId} must identify a legitimate stream id for the remote endpoint to be creating</li> * <li>{@code streamId} is greater than the Last Known Stream ID which was sent by the local endpoint * in the last {@code GOAWAY} frame</li> * </ul> * <p/> */
private boolean streamCreatedAfterGoAwaySent(int streamId) { Endpoint<?> remote = connection.remote(); return connection.goAwaySent() && remote.isValidStreamId(streamId) && streamId > remote.lastStreamKnownByPeer(); } private void verifyStreamMayHaveExisted(int streamId) throws Http2Exception { if (!connection.streamMayHaveExisted(streamId)) { throw connectionError(PROTOCOL_ERROR, "Stream %d does not exist", streamId); } } } private final class PrefaceFrameListener implements Http2FrameListener {
Verifies that the HTTP/2 connection preface has been received from the remote endpoint. It is possible that the current call to Http2FrameReader.readFrame(ChannelHandlerContext, ByteBuf, Http2FrameListener) will have multiple frames to dispatch. So it may be OK for this class to get legitimate frames for the first readFrame.
/** * Verifies that the HTTP/2 connection preface has been received from the remote endpoint. * It is possible that the current call to * {@link Http2FrameReader#readFrame(ChannelHandlerContext, ByteBuf, Http2FrameListener)} will have multiple * frames to dispatch. So it may be OK for this class to get legitimate frames for the first readFrame. */
private void verifyPrefaceReceived() throws Http2Exception { if (!prefaceReceived()) { throw connectionError(PROTOCOL_ERROR, "Received non-SETTINGS as first frame."); } } @Override public int onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, boolean endOfStream) throws Http2Exception { verifyPrefaceReceived(); return internalFrameListener.onDataRead(ctx, streamId, data, padding, endOfStream); } @Override public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int padding, boolean endOfStream) throws Http2Exception { verifyPrefaceReceived(); internalFrameListener.onHeadersRead(ctx, streamId, headers, padding, endOfStream); } @Override public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int streamDependency, short weight, boolean exclusive, int padding, boolean endOfStream) throws Http2Exception { verifyPrefaceReceived(); internalFrameListener.onHeadersRead(ctx, streamId, headers, streamDependency, weight, exclusive, padding, endOfStream); } @Override public void onPriorityRead(ChannelHandlerContext ctx, int streamId, int streamDependency, short weight, boolean exclusive) throws Http2Exception { verifyPrefaceReceived(); internalFrameListener.onPriorityRead(ctx, streamId, streamDependency, weight, exclusive); } @Override public void onRstStreamRead(ChannelHandlerContext ctx, int streamId, long errorCode) throws Http2Exception { verifyPrefaceReceived(); internalFrameListener.onRstStreamRead(ctx, streamId, errorCode); } @Override public void onSettingsAckRead(ChannelHandlerContext ctx) throws Http2Exception { verifyPrefaceReceived(); internalFrameListener.onSettingsAckRead(ctx); } @Override public void onSettingsRead(ChannelHandlerContext ctx, Http2Settings settings) throws Http2Exception { // The first settings should change the internalFrameListener to the "real" listener // that expects the preface to be verified. if (!prefaceReceived()) { internalFrameListener = new FrameReadListener(); } internalFrameListener.onSettingsRead(ctx, settings); } @Override public void onPingRead(ChannelHandlerContext ctx, long data) throws Http2Exception { verifyPrefaceReceived(); internalFrameListener.onPingRead(ctx, data); } @Override public void onPingAckRead(ChannelHandlerContext ctx, long data) throws Http2Exception { verifyPrefaceReceived(); internalFrameListener.onPingAckRead(ctx, data); } @Override public void onPushPromiseRead(ChannelHandlerContext ctx, int streamId, int promisedStreamId, Http2Headers headers, int padding) throws Http2Exception { verifyPrefaceReceived(); internalFrameListener.onPushPromiseRead(ctx, streamId, promisedStreamId, headers, padding); } @Override public void onGoAwayRead(ChannelHandlerContext ctx, int lastStreamId, long errorCode, ByteBuf debugData) throws Http2Exception { onGoAwayRead0(ctx, lastStreamId, errorCode, debugData); } @Override public void onWindowUpdateRead(ChannelHandlerContext ctx, int streamId, int windowSizeIncrement) throws Http2Exception { verifyPrefaceReceived(); internalFrameListener.onWindowUpdateRead(ctx, streamId, windowSizeIncrement); } @Override public void onUnknownFrame(ChannelHandlerContext ctx, byte frameType, int streamId, Http2Flags flags, ByteBuf payload) throws Http2Exception { onUnknownFrame0(ctx, frameType, streamId, flags, payload); } } }