View Javadoc
1   /*
2    * Copyright 2014 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
5    * "License"); you may not use this file except in compliance with the License. You may obtain a
6    * copy of the License at:
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software distributed under the License
11   * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12   * or implied. See the License for the specific language governing permissions and limitations under
13   * the License.
14   */
15  package io.netty.handler.codec.http2;
16  
17  import io.netty.buffer.ByteBuf;
18  import io.netty.channel.ChannelHandlerContext;
19  import io.netty.handler.codec.http.HttpStatusClass;
20  import io.netty.handler.codec.http2.Http2Connection.Endpoint;
21  import io.netty.util.internal.UnstableApi;
22  import io.netty.util.internal.logging.InternalLogger;
23  import io.netty.util.internal.logging.InternalLoggerFactory;
24  
25  import java.util.List;
26  
27  import static io.netty.handler.codec.http.HttpStatusClass.INFORMATIONAL;
28  import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_PRIORITY_WEIGHT;
29  import static io.netty.handler.codec.http2.Http2Error.INTERNAL_ERROR;
30  import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
31  import static io.netty.handler.codec.http2.Http2Error.STREAM_CLOSED;
32  import static io.netty.handler.codec.http2.Http2Exception.connectionError;
33  import static io.netty.handler.codec.http2.Http2Exception.streamError;
34  import static io.netty.handler.codec.http2.Http2PromisedRequestVerifier.ALWAYS_VERIFY;
35  import static io.netty.handler.codec.http2.Http2Stream.State.CLOSED;
36  import static io.netty.handler.codec.http2.Http2Stream.State.HALF_CLOSED_REMOTE;
37  import static io.netty.util.internal.ObjectUtil.checkNotNull;
38  import static java.lang.Integer.MAX_VALUE;
39  import static java.lang.Math.min;
40  
41  /**
42   * Provides the default implementation for processing inbound frame events and delegates to a
43   * {@link Http2FrameListener}
44   * <p>
45   * This class will read HTTP/2 frames and delegate the events to a {@link Http2FrameListener}
46   * <p>
47   * This interface enforces inbound flow control functionality through
48   * {@link Http2LocalFlowController}
49   */
50  @UnstableApi
51  public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder {
52      private static final InternalLogger logger = InternalLoggerFactory.getInstance(DefaultHttp2ConnectionDecoder.class);
53      private Http2FrameListener internalFrameListener = new PrefaceFrameListener();
54      private final Http2Connection connection;
55      private Http2LifecycleManager lifecycleManager;
56      private final Http2ConnectionEncoder encoder;
57      private final Http2FrameReader frameReader;
58      private Http2FrameListener listener;
59      private final Http2PromisedRequestVerifier requestVerifier;
60  
61      public DefaultHttp2ConnectionDecoder(Http2Connection connection,
62                                           Http2ConnectionEncoder encoder,
63                                           Http2FrameReader frameReader) {
64          this(connection, encoder, frameReader, ALWAYS_VERIFY);
65      }
66  
67      public DefaultHttp2ConnectionDecoder(Http2Connection connection,
68                                           Http2ConnectionEncoder encoder,
69                                           Http2FrameReader frameReader,
70                                           Http2PromisedRequestVerifier requestVerifier) {
71          this.connection = checkNotNull(connection, "connection");
72          this.frameReader = checkNotNull(frameReader, "frameReader");
73          this.encoder = checkNotNull(encoder, "encoder");
74          this.requestVerifier = checkNotNull(requestVerifier, "requestVerifier");
75          if (connection.local().flowController() == null) {
76              connection.local().flowController(new DefaultHttp2LocalFlowController(connection));
77          }
78          connection.local().flowController().frameWriter(encoder.frameWriter());
79      }
80  
81      @Override
82      public void lifecycleManager(Http2LifecycleManager lifecycleManager) {
83          this.lifecycleManager = checkNotNull(lifecycleManager, "lifecycleManager");
84      }
85  
86      @Override
87      public Http2Connection connection() {
88          return connection;
89      }
90  
91      @Override
92      public final Http2LocalFlowController flowController() {
93          return connection.local().flowController();
94      }
95  
96      @Override
97      public void frameListener(Http2FrameListener listener) {
98          this.listener = checkNotNull(listener, "listener");
99      }
100 
101     @Override
102     public Http2FrameListener frameListener() {
103         return listener;
104     }
105 
106     // Visible for testing
107     Http2FrameListener internalFrameListener() {
108         return internalFrameListener;
109     }
110 
111     @Override
112     public boolean prefaceReceived() {
113         return FrameReadListener.class == internalFrameListener.getClass();
114     }
115 
116     @Override
117     public void decodeFrame(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Http2Exception {
118         frameReader.readFrame(ctx, in, internalFrameListener);
119     }
120 
121     @Override
122     public Http2Settings localSettings() {
123         Http2Settings settings = new Http2Settings();
124         Http2FrameReader.Configuration config = frameReader.configuration();
125         Http2HeadersDecoder.Configuration headersConfig = config.headersConfiguration();
126         Http2FrameSizePolicy frameSizePolicy = config.frameSizePolicy();
127         settings.initialWindowSize(flowController().initialWindowSize());
128         settings.maxConcurrentStreams(connection.remote().maxActiveStreams());
129         settings.headerTableSize(headersConfig.maxHeaderTableSize());
130         settings.maxFrameSize(frameSizePolicy.maxFrameSize());
131         settings.maxHeaderListSize(headersConfig.maxHeaderListSize());
132         if (!connection.isServer()) {
133             // Only set the pushEnabled flag if this is a client endpoint.
134             settings.pushEnabled(connection.local().allowPushTo());
135         }
136         return settings;
137     }
138 
139     @Override
140     public void close() {
141         frameReader.close();
142     }
143 
144     /**
145      * Calculate the threshold in bytes which should trigger a {@code GO_AWAY} if a set of headers exceeds this amount.
146      * @param maxHeaderListSize
147      *      <a href="https://tools.ietf.org/html/rfc7540#section-6.5.2">SETTINGS_MAX_HEADER_LIST_SIZE</a> for the local
148      *      endpoint.
149      * @return the threshold in bytes which should trigger a {@code GO_AWAY} if a set of headers exceeds this amount.
150      */
151     protected long calculateMaxHeaderListSizeGoAway(long maxHeaderListSize) {
152         return Http2CodecUtil.calculateMaxHeaderListSizeGoAway(maxHeaderListSize);
153     }
154 
155     private int unconsumedBytes(Http2Stream stream) {
156         return flowController().unconsumedBytes(stream);
157     }
158 
159     void onGoAwayRead0(ChannelHandlerContext ctx, int lastStreamId, long errorCode, ByteBuf debugData)
160             throws Http2Exception {
161         connection.goAwayReceived(lastStreamId, errorCode, debugData);
162         listener.onGoAwayRead(ctx, lastStreamId, errorCode, debugData);
163     }
164 
165     void onUnknownFrame0(ChannelHandlerContext ctx, byte frameType, int streamId, Http2Flags flags,
166             ByteBuf payload) throws Http2Exception {
167         listener.onUnknownFrame(ctx, frameType, streamId, flags, payload);
168     }
169 
170     /**
171      * Handles all inbound frames from the network.
172      */
173     private final class FrameReadListener implements Http2FrameListener {
174         @Override
175         public int onDataRead(final ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
176                               boolean endOfStream) throws Http2Exception {
177             Http2Stream stream = connection.stream(streamId);
178             Http2LocalFlowController flowController = flowController();
179             int bytesToReturn = data.readableBytes() + padding;
180 
181             final boolean shouldIgnore;
182             try {
183                 shouldIgnore = shouldIgnoreHeadersOrDataFrame(ctx, streamId, stream, "DATA");
184             } catch (Http2Exception e) {
185                 // Ignoring this frame. We still need to count the frame towards the connection flow control
186                 // window, but we immediately mark all bytes as consumed.
187                 flowController.receiveFlowControlledFrame(stream, data, padding, endOfStream);
188                 flowController.consumeBytes(stream, bytesToReturn);
189                 throw e;
190             } catch (Throwable t) {
191                 throw connectionError(INTERNAL_ERROR, t, "Unhandled error on data stream id %d", streamId);
192             }
193 
194             if (shouldIgnore) {
195                 // Ignoring this frame. We still need to count the frame towards the connection flow control
196                 // window, but we immediately mark all bytes as consumed.
197                 flowController.receiveFlowControlledFrame(stream, data, padding, endOfStream);
198                 flowController.consumeBytes(stream, bytesToReturn);
199 
200                 // Verify that the stream may have existed after we apply flow control.
201                 verifyStreamMayHaveExisted(streamId);
202 
203                 // All bytes have been consumed.
204                 return bytesToReturn;
205             }
206 
207             Http2Exception error = null;
208             switch (stream.state()) {
209                 case OPEN:
210                 case HALF_CLOSED_LOCAL:
211                     break;
212                 case HALF_CLOSED_REMOTE:
213                 case CLOSED:
214                     error = streamError(stream.id(), STREAM_CLOSED, "Stream %d in unexpected state: %s",
215                         stream.id(), stream.state());
216                     break;
217                 default:
218                     error = streamError(stream.id(), PROTOCOL_ERROR,
219                         "Stream %d in unexpected state: %s", stream.id(), stream.state());
220                     break;
221             }
222 
223             int unconsumedBytes = unconsumedBytes(stream);
224             try {
225                 flowController.receiveFlowControlledFrame(stream, data, padding, endOfStream);
226                 // Update the unconsumed bytes after flow control is applied.
227                 unconsumedBytes = unconsumedBytes(stream);
228 
229                 // If the stream is in an invalid state to receive the frame, throw the error.
230                 if (error != null) {
231                     throw error;
232                 }
233 
234                 // Call back the application and retrieve the number of bytes that have been
235                 // immediately processed.
236                 bytesToReturn = listener.onDataRead(ctx, streamId, data, padding, endOfStream);
237                 return bytesToReturn;
238             } catch (Http2Exception e) {
239                 // If an exception happened during delivery, the listener may have returned part
240                 // of the bytes before the error occurred. If that's the case, subtract that from
241                 // the total processed bytes so that we don't return too many bytes.
242                 int delta = unconsumedBytes - unconsumedBytes(stream);
243                 bytesToReturn -= delta;
244                 throw e;
245             } catch (RuntimeException e) {
246                 // If an exception happened during delivery, the listener may have returned part
247                 // of the bytes before the error occurred. If that's the case, subtract that from
248                 // the total processed bytes so that we don't return too many bytes.
249                 int delta = unconsumedBytes - unconsumedBytes(stream);
250                 bytesToReturn -= delta;
251                 throw e;
252             } finally {
253                 // If appropriate, return the processed bytes to the flow controller.
254                 flowController.consumeBytes(stream, bytesToReturn);
255 
256                 if (endOfStream) {
257                     lifecycleManager.closeStreamRemote(stream, ctx.newSucceededFuture());
258                 }
259             }
260         }
261 
262         @Override
263         public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int padding,
264                 boolean endOfStream) throws Http2Exception {
265             onHeadersRead(ctx, streamId, headers, 0, DEFAULT_PRIORITY_WEIGHT, false, padding, endOfStream);
266         }
267 
268         @Override
269         public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int streamDependency,
270                 short weight, boolean exclusive, int padding, boolean endOfStream) throws Http2Exception {
271             Http2Stream stream = connection.stream(streamId);
272             boolean allowHalfClosedRemote = false;
273             if (stream == null && !connection.streamMayHaveExisted(streamId)) {
274                 stream = connection.remote().createStream(streamId, endOfStream);
275                 // Allow the state to be HALF_CLOSE_REMOTE if we're creating it in that state.
276                 allowHalfClosedRemote = stream.state() == HALF_CLOSED_REMOTE;
277             }
278 
279             if (shouldIgnoreHeadersOrDataFrame(ctx, streamId, stream, "HEADERS")) {
280                 return;
281             }
282 
283             boolean isInformational = !connection.isServer() &&
284                     HttpStatusClass.valueOf(headers.status()) == INFORMATIONAL;
285             if ((isInformational || !endOfStream) && stream.isHeadersReceived() || stream.isTrailersReceived()) {
286                 throw streamError(streamId, PROTOCOL_ERROR,
287                                   "Stream %d received too many headers EOS: %s state: %s",
288                                   streamId, endOfStream, stream.state());
289             }
290 
291             switch (stream.state()) {
292                 case RESERVED_REMOTE:
293                     stream.open(endOfStream);
294                     break;
295                 case OPEN:
296                 case HALF_CLOSED_LOCAL:
297                     // Allowed to receive headers in these states.
298                     break;
299                 case HALF_CLOSED_REMOTE:
300                     if (!allowHalfClosedRemote) {
301                         throw streamError(stream.id(), STREAM_CLOSED, "Stream %d in unexpected state: %s",
302                                 stream.id(), stream.state());
303                     }
304                     break;
305                 case CLOSED:
306                     throw streamError(stream.id(), STREAM_CLOSED, "Stream %d in unexpected state: %s",
307                             stream.id(), stream.state());
308                 default:
309                     // Connection error.
310                     throw connectionError(PROTOCOL_ERROR, "Stream %d in unexpected state: %s", stream.id(),
311                             stream.state());
312             }
313 
314             stream.headersReceived(isInformational);
315             encoder.flowController().updateDependencyTree(streamId, streamDependency, weight, exclusive);
316 
317             listener.onHeadersRead(ctx, streamId, headers, streamDependency, weight, exclusive, padding, endOfStream);
318 
319             // If the headers completes this stream, close it.
320             if (endOfStream) {
321                 lifecycleManager.closeStreamRemote(stream, ctx.newSucceededFuture());
322             }
323         }
324 
325         @Override
326         public void onPriorityRead(ChannelHandlerContext ctx, int streamId, int streamDependency, short weight,
327                 boolean exclusive) throws Http2Exception {
328             encoder.flowController().updateDependencyTree(streamId, streamDependency, weight, exclusive);
329 
330             listener.onPriorityRead(ctx, streamId, streamDependency, weight, exclusive);
331         }
332 
333         @Override
334         public void onRstStreamRead(ChannelHandlerContext ctx, int streamId, long errorCode) throws Http2Exception {
335             Http2Stream stream = connection.stream(streamId);
336             if (stream == null) {
337                 verifyStreamMayHaveExisted(streamId);
338                 return;
339             }
340 
341             switch(stream.state()) {
342             case IDLE:
343                 throw connectionError(PROTOCOL_ERROR, "RST_STREAM received for IDLE stream %d", streamId);
344             case CLOSED:
345                 return; // RST_STREAM frames must be ignored for closed streams.
346             default:
347                 break;
348             }
349 
350             listener.onRstStreamRead(ctx, streamId, errorCode);
351 
352             lifecycleManager.closeStream(stream, ctx.newSucceededFuture());
353         }
354 
355         @Override
356         public void onSettingsAckRead(ChannelHandlerContext ctx) throws Http2Exception {
357             // Apply oldest outstanding local settings here. This is a synchronization point between endpoints.
358             Http2Settings settings = encoder.pollSentSettings();
359 
360             if (settings != null) {
361                 applyLocalSettings(settings);
362             }
363 
364             listener.onSettingsAckRead(ctx);
365         }
366 
367         /**
368          * Applies settings sent from the local endpoint.
369          * <p>
370          * This method is only called after the local settings have been acknowledged from the remote endpoint.
371          */
372         private void applyLocalSettings(Http2Settings settings) throws Http2Exception {
373             Boolean pushEnabled = settings.pushEnabled();
374             final Http2FrameReader.Configuration config = frameReader.configuration();
375             final Http2HeadersDecoder.Configuration headerConfig = config.headersConfiguration();
376             final Http2FrameSizePolicy frameSizePolicy = config.frameSizePolicy();
377             if (pushEnabled != null) {
378                 if (connection.isServer()) {
379                     throw connectionError(PROTOCOL_ERROR, "Server sending SETTINGS frame with ENABLE_PUSH specified");
380                 }
381                 connection.local().allowPushTo(pushEnabled);
382             }
383 
384             Long maxConcurrentStreams = settings.maxConcurrentStreams();
385             if (maxConcurrentStreams != null) {
386                 connection.remote().maxActiveStreams((int) min(maxConcurrentStreams, MAX_VALUE));
387             }
388 
389             Long headerTableSize = settings.headerTableSize();
390             if (headerTableSize != null) {
391                 headerConfig.maxHeaderTableSize(headerTableSize);
392             }
393 
394             Long maxHeaderListSize = settings.maxHeaderListSize();
395             if (maxHeaderListSize != null) {
396                 headerConfig.maxHeaderListSize(maxHeaderListSize, calculateMaxHeaderListSizeGoAway(maxHeaderListSize));
397             }
398 
399             Integer maxFrameSize = settings.maxFrameSize();
400             if (maxFrameSize != null) {
401                 frameSizePolicy.maxFrameSize(maxFrameSize);
402             }
403 
404             Integer initialWindowSize = settings.initialWindowSize();
405             if (initialWindowSize != null) {
406                 flowController().initialWindowSize(initialWindowSize);
407             }
408         }
409 
410         @Override
411         public void onSettingsRead(ChannelHandlerContext ctx, Http2Settings settings) throws Http2Exception {
412             // Acknowledge receipt of the settings. We should do this before we process the settings to ensure our
413             // remote peer applies these settings before any subsequent frames that we may send which depend upon these
414             // new settings. See https://github.com/netty/netty/issues/6520.
415             encoder.writeSettingsAck(ctx, ctx.newPromise());
416 
417             encoder.remoteSettings(settings);
418 
419             listener.onSettingsRead(ctx, settings);
420         }
421 
422         @Override
423         public void onPingRead(ChannelHandlerContext ctx, long data) throws Http2Exception {
424             // Send an ack back to the remote client.
425             // Need to retain the buffer here since it will be released after the write completes.
426             encoder.writePing(ctx, true, data, ctx.newPromise());
427 
428             listener.onPingRead(ctx, data);
429         }
430 
431         @Override
432         public void onPingAckRead(ChannelHandlerContext ctx, long data) throws Http2Exception {
433             listener.onPingAckRead(ctx, data);
434         }
435 
436         @Override
437         public void onPushPromiseRead(ChannelHandlerContext ctx, int streamId, int promisedStreamId,
438                 Http2Headers headers, int padding) throws Http2Exception {
439             // A client cannot push.
440             if (connection().isServer()) {
441                 throw connectionError(PROTOCOL_ERROR, "A client cannot push.");
442             }
443 
444             Http2Stream parentStream = connection.stream(streamId);
445 
446             if (shouldIgnoreHeadersOrDataFrame(ctx, streamId, parentStream, "PUSH_PROMISE")) {
447                 return;
448             }
449 
450             if (parentStream == null) {
451                 throw connectionError(PROTOCOL_ERROR, "Stream %d does not exist", streamId);
452             }
453 
454             switch (parentStream.state()) {
455               case OPEN:
456               case HALF_CLOSED_LOCAL:
457                   // Allowed to receive push promise in these states.
458                   break;
459               default:
460                   // Connection error.
461                   throw connectionError(PROTOCOL_ERROR,
462                       "Stream %d in unexpected state for receiving push promise: %s",
463                       parentStream.id(), parentStream.state());
464             }
465 
466             if (!requestVerifier.isAuthoritative(ctx, headers)) {
467                 throw streamError(promisedStreamId, PROTOCOL_ERROR,
468                         "Promised request on stream %d for promised stream %d is not authoritative",
469                         streamId, promisedStreamId);
470             }
471             if (!requestVerifier.isCacheable(headers)) {
472                 throw streamError(promisedStreamId, PROTOCOL_ERROR,
473                         "Promised request on stream %d for promised stream %d is not known to be cacheable",
474                         streamId, promisedStreamId);
475             }
476             if (!requestVerifier.isSafe(headers)) {
477                 throw streamError(promisedStreamId, PROTOCOL_ERROR,
478                         "Promised request on stream %d for promised stream %d is not known to be safe",
479                         streamId, promisedStreamId);
480             }
481 
482             // Reserve the push stream based with a priority based on the current stream's priority.
483             connection.remote().reservePushStream(promisedStreamId, parentStream);
484 
485             listener.onPushPromiseRead(ctx, streamId, promisedStreamId, headers, padding);
486         }
487 
488         @Override
489         public void onGoAwayRead(ChannelHandlerContext ctx, int lastStreamId, long errorCode, ByteBuf debugData)
490                 throws Http2Exception {
491             onGoAwayRead0(ctx, lastStreamId, errorCode, debugData);
492         }
493 
494         @Override
495         public void onWindowUpdateRead(ChannelHandlerContext ctx, int streamId, int windowSizeIncrement)
496                 throws Http2Exception {
497             Http2Stream stream = connection.stream(streamId);
498             if (stream == null || stream.state() == CLOSED || streamCreatedAfterGoAwaySent(streamId)) {
499                 // Ignore this frame.
500                 verifyStreamMayHaveExisted(streamId);
501                 return;
502             }
503 
504             // Update the outbound flow control window.
505             encoder.flowController().incrementWindowSize(stream, windowSizeIncrement);
506 
507             listener.onWindowUpdateRead(ctx, streamId, windowSizeIncrement);
508         }
509 
510         @Override
511         public void onUnknownFrame(ChannelHandlerContext ctx, byte frameType, int streamId, Http2Flags flags,
512                 ByteBuf payload) throws Http2Exception {
513             onUnknownFrame0(ctx, frameType, streamId, flags, payload);
514         }
515 
516         /**
517          * Helper method to determine if a frame that has the semantics of headers or data should be ignored for the
518          * {@code stream} (which may be {@code null}) associated with {@code streamId}.
519          */
520         private boolean shouldIgnoreHeadersOrDataFrame(ChannelHandlerContext ctx, int streamId, Http2Stream stream,
521                 String frameName) throws Http2Exception {
522             if (stream == null) {
523                 if (streamCreatedAfterGoAwaySent(streamId)) {
524                     logger.info("{} ignoring {} frame for stream {}. Stream sent after GOAWAY sent",
525                             ctx.channel(), frameName, streamId);
526                     return true;
527                 }
528                 // Its possible that this frame would result in stream ID out of order creation (PROTOCOL ERROR) and its
529                 // also possible that this frame is received on a CLOSED stream (STREAM_CLOSED after a RST_STREAM is
530                 // sent). We don't have enough information to know for sure, so we choose the lesser of the two errors.
531                 throw streamError(streamId, STREAM_CLOSED, "Received %s frame for an unknown stream %d",
532                                   frameName, streamId);
533             } else if (stream.isResetSent() || streamCreatedAfterGoAwaySent(streamId)) {
534                 // If we have sent a reset stream it is assumed the stream will be closed after the write completes.
535                 // If we have not sent a reset, but the stream was created after a GoAway this is not supported by
536                 // DefaultHttp2Connection and if a custom Http2Connection is used it is assumed the lifetime is managed
537                 // elsewhere so we don't close the stream or otherwise modify the stream's state.
538 
539                 if (logger.isInfoEnabled()) {
540                     logger.info("{} ignoring {} frame for stream {} {}", ctx.channel(), frameName,
541                             stream.isResetSent() ? "RST_STREAM sent." :
542                                 ("Stream created after GOAWAY sent. Last known stream by peer " +
543                                  connection.remote().lastStreamKnownByPeer()));
544                 }
545 
546                 return true;
547             }
548             return false;
549         }
550 
551         /**
552          * Helper method for determining whether or not to ignore inbound frames. A stream is considered to be created
553          * after a {@code GOAWAY} is sent if the following conditions hold:
554          * <p/>
555          * <ul>
556          *     <li>A {@code GOAWAY} must have been sent by the local endpoint</li>
557          *     <li>The {@code streamId} must identify a legitimate stream id for the remote endpoint to be creating</li>
558          *     <li>{@code streamId} is greater than the Last Known Stream ID which was sent by the local endpoint
559          *     in the last {@code GOAWAY} frame</li>
560          * </ul>
561          * <p/>
562          */
563         private boolean streamCreatedAfterGoAwaySent(int streamId) {
564             Endpoint<?> remote = connection.remote();
565             return connection.goAwaySent() && remote.isValidStreamId(streamId) &&
566                     streamId > remote.lastStreamKnownByPeer();
567         }
568 
569         private void verifyStreamMayHaveExisted(int streamId) throws Http2Exception {
570             if (!connection.streamMayHaveExisted(streamId)) {
571                 throw connectionError(PROTOCOL_ERROR, "Stream %d does not exist", streamId);
572             }
573         }
574     }
575 
576     private final class PrefaceFrameListener implements Http2FrameListener {
577         /**
578          * Verifies that the HTTP/2 connection preface has been received from the remote endpoint.
579          * It is possible that the current call to
580          * {@link Http2FrameReader#readFrame(ChannelHandlerContext, ByteBuf, Http2FrameListener)} will have multiple
581          * frames to dispatch. So it may be OK for this class to get legitimate frames for the first readFrame.
582          */
583         private void verifyPrefaceReceived() throws Http2Exception {
584             if (!prefaceReceived()) {
585                 throw connectionError(PROTOCOL_ERROR, "Received non-SETTINGS as first frame.");
586             }
587         }
588 
589         @Override
590         public int onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, boolean endOfStream)
591                 throws Http2Exception {
592             verifyPrefaceReceived();
593             return internalFrameListener.onDataRead(ctx, streamId, data, padding, endOfStream);
594         }
595 
596         @Override
597         public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int padding,
598                 boolean endOfStream) throws Http2Exception {
599             verifyPrefaceReceived();
600             internalFrameListener.onHeadersRead(ctx, streamId, headers, padding, endOfStream);
601         }
602 
603         @Override
604         public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int streamDependency,
605                 short weight, boolean exclusive, int padding, boolean endOfStream) throws Http2Exception {
606             verifyPrefaceReceived();
607             internalFrameListener.onHeadersRead(ctx, streamId, headers, streamDependency, weight,
608                     exclusive, padding, endOfStream);
609         }
610 
611         @Override
612         public void onPriorityRead(ChannelHandlerContext ctx, int streamId, int streamDependency, short weight,
613                 boolean exclusive) throws Http2Exception {
614             verifyPrefaceReceived();
615             internalFrameListener.onPriorityRead(ctx, streamId, streamDependency, weight, exclusive);
616         }
617 
618         @Override
619         public void onRstStreamRead(ChannelHandlerContext ctx, int streamId, long errorCode) throws Http2Exception {
620             verifyPrefaceReceived();
621             internalFrameListener.onRstStreamRead(ctx, streamId, errorCode);
622         }
623 
624         @Override
625         public void onSettingsAckRead(ChannelHandlerContext ctx) throws Http2Exception {
626             verifyPrefaceReceived();
627             internalFrameListener.onSettingsAckRead(ctx);
628         }
629 
630         @Override
631         public void onSettingsRead(ChannelHandlerContext ctx, Http2Settings settings) throws Http2Exception {
632             // The first settings should change the internalFrameListener to the "real" listener
633             // that expects the preface to be verified.
634             if (!prefaceReceived()) {
635                 internalFrameListener = new FrameReadListener();
636             }
637             internalFrameListener.onSettingsRead(ctx, settings);
638         }
639 
640         @Override
641         public void onPingRead(ChannelHandlerContext ctx, long data) throws Http2Exception {
642             verifyPrefaceReceived();
643             internalFrameListener.onPingRead(ctx, data);
644         }
645 
646         @Override
647         public void onPingAckRead(ChannelHandlerContext ctx, long data) throws Http2Exception {
648             verifyPrefaceReceived();
649             internalFrameListener.onPingAckRead(ctx, data);
650         }
651 
652         @Override
653         public void onPushPromiseRead(ChannelHandlerContext ctx, int streamId, int promisedStreamId,
654                 Http2Headers headers, int padding) throws Http2Exception {
655             verifyPrefaceReceived();
656             internalFrameListener.onPushPromiseRead(ctx, streamId, promisedStreamId, headers, padding);
657         }
658 
659         @Override
660         public void onGoAwayRead(ChannelHandlerContext ctx, int lastStreamId, long errorCode, ByteBuf debugData)
661                 throws Http2Exception {
662             onGoAwayRead0(ctx, lastStreamId, errorCode, debugData);
663         }
664 
665         @Override
666         public void onWindowUpdateRead(ChannelHandlerContext ctx, int streamId, int windowSizeIncrement)
667                 throws Http2Exception {
668             verifyPrefaceReceived();
669             internalFrameListener.onWindowUpdateRead(ctx, streamId, windowSizeIncrement);
670         }
671 
672         @Override
673         public void onUnknownFrame(ChannelHandlerContext ctx, byte frameType, int streamId, Http2Flags flags,
674                 ByteBuf payload) throws Http2Exception {
675             onUnknownFrame0(ctx, frameType, streamId, flags, payload);
676         }
677     }
678 }