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         if (connection.goAwayReceived() && connection.local().lastStreamKnownByPeer() < lastStreamId) {
162             throw connectionError(PROTOCOL_ERROR, "lastStreamId MUST NOT increase. Current value: %d new value: %d",
163                     connection.local().lastStreamKnownByPeer(), lastStreamId);
164         }
165         listener.onGoAwayRead(ctx, lastStreamId, errorCode, debugData);
166         connection.goAwayReceived(lastStreamId, errorCode, debugData);
167     }
168 
169     void onUnknownFrame0(ChannelHandlerContext ctx, byte frameType, int streamId, Http2Flags flags,
170             ByteBuf payload) throws Http2Exception {
171         listener.onUnknownFrame(ctx, frameType, streamId, flags, payload);
172     }
173 
174     /**
175      * Handles all inbound frames from the network.
176      */
177     private final class FrameReadListener implements Http2FrameListener {
178         @Override
179         public int onDataRead(final ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
180                               boolean endOfStream) throws Http2Exception {
181             Http2Stream stream = connection.stream(streamId);
182             Http2LocalFlowController flowController = flowController();
183             int bytesToReturn = data.readableBytes() + padding;
184 
185             final boolean shouldIgnore;
186             try {
187                 shouldIgnore = shouldIgnoreHeadersOrDataFrame(ctx, streamId, stream, "DATA");
188             } catch (Http2Exception e) {
189                 // Ignoring this frame. We still need to count the frame towards the connection flow control
190                 // window, but we immediately mark all bytes as consumed.
191                 flowController.receiveFlowControlledFrame(stream, data, padding, endOfStream);
192                 flowController.consumeBytes(stream, bytesToReturn);
193                 throw e;
194             } catch (Throwable t) {
195                 throw connectionError(INTERNAL_ERROR, t, "Unhandled error on data stream id %d", streamId);
196             }
197 
198             if (shouldIgnore) {
199                 // Ignoring this frame. We still need to count the frame towards the connection flow control
200                 // window, but we immediately mark all bytes as consumed.
201                 flowController.receiveFlowControlledFrame(stream, data, padding, endOfStream);
202                 flowController.consumeBytes(stream, bytesToReturn);
203 
204                 // Verify that the stream may have existed after we apply flow control.
205                 verifyStreamMayHaveExisted(streamId);
206 
207                 // All bytes have been consumed.
208                 return bytesToReturn;
209             }
210 
211             Http2Exception error = null;
212             switch (stream.state()) {
213                 case OPEN:
214                 case HALF_CLOSED_LOCAL:
215                     break;
216                 case HALF_CLOSED_REMOTE:
217                 case CLOSED:
218                     error = streamError(stream.id(), STREAM_CLOSED, "Stream %d in unexpected state: %s",
219                         stream.id(), stream.state());
220                     break;
221                 default:
222                     error = streamError(stream.id(), PROTOCOL_ERROR,
223                         "Stream %d in unexpected state: %s", stream.id(), stream.state());
224                     break;
225             }
226 
227             int unconsumedBytes = unconsumedBytes(stream);
228             try {
229                 flowController.receiveFlowControlledFrame(stream, data, padding, endOfStream);
230                 // Update the unconsumed bytes after flow control is applied.
231                 unconsumedBytes = unconsumedBytes(stream);
232 
233                 // If the stream is in an invalid state to receive the frame, throw the error.
234                 if (error != null) {
235                     throw error;
236                 }
237 
238                 // Call back the application and retrieve the number of bytes that have been
239                 // immediately processed.
240                 bytesToReturn = listener.onDataRead(ctx, streamId, data, padding, endOfStream);
241                 return bytesToReturn;
242             } catch (Http2Exception e) {
243                 // If an exception happened during delivery, the listener may have returned part
244                 // of the bytes before the error occurred. If that's the case, subtract that from
245                 // the total processed bytes so that we don't return too many bytes.
246                 int delta = unconsumedBytes - unconsumedBytes(stream);
247                 bytesToReturn -= delta;
248                 throw e;
249             } catch (RuntimeException e) {
250                 // If an exception happened during delivery, the listener may have returned part
251                 // of the bytes before the error occurred. If that's the case, subtract that from
252                 // the total processed bytes so that we don't return too many bytes.
253                 int delta = unconsumedBytes - unconsumedBytes(stream);
254                 bytesToReturn -= delta;
255                 throw e;
256             } finally {
257                 // If appropriate, return the processed bytes to the flow controller.
258                 flowController.consumeBytes(stream, bytesToReturn);
259 
260                 if (endOfStream) {
261                     lifecycleManager.closeStreamRemote(stream, ctx.newSucceededFuture());
262                 }
263             }
264         }
265 
266         @Override
267         public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int padding,
268                 boolean endOfStream) throws Http2Exception {
269             onHeadersRead(ctx, streamId, headers, 0, DEFAULT_PRIORITY_WEIGHT, false, padding, endOfStream);
270         }
271 
272         @Override
273         public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int streamDependency,
274                 short weight, boolean exclusive, int padding, boolean endOfStream) throws Http2Exception {
275             Http2Stream stream = connection.stream(streamId);
276             boolean allowHalfClosedRemote = false;
277             if (stream == null && !connection.streamMayHaveExisted(streamId)) {
278                 stream = connection.remote().createStream(streamId, endOfStream);
279                 // Allow the state to be HALF_CLOSE_REMOTE if we're creating it in that state.
280                 allowHalfClosedRemote = stream.state() == HALF_CLOSED_REMOTE;
281             }
282 
283             if (shouldIgnoreHeadersOrDataFrame(ctx, streamId, stream, "HEADERS")) {
284                 return;
285             }
286 
287             boolean isInformational = !connection.isServer() &&
288                     HttpStatusClass.valueOf(headers.status()) == INFORMATIONAL;
289             if ((isInformational || !endOfStream) && stream.isHeadersReceived() || stream.isTrailersReceived()) {
290                 throw streamError(streamId, PROTOCOL_ERROR,
291                                   "Stream %d received too many headers EOS: %s state: %s",
292                                   streamId, endOfStream, stream.state());
293             }
294 
295             switch (stream.state()) {
296                 case RESERVED_REMOTE:
297                     stream.open(endOfStream);
298                     break;
299                 case OPEN:
300                 case HALF_CLOSED_LOCAL:
301                     // Allowed to receive headers in these states.
302                     break;
303                 case HALF_CLOSED_REMOTE:
304                     if (!allowHalfClosedRemote) {
305                         throw streamError(stream.id(), STREAM_CLOSED, "Stream %d in unexpected state: %s",
306                                 stream.id(), stream.state());
307                     }
308                     break;
309                 case CLOSED:
310                     throw streamError(stream.id(), STREAM_CLOSED, "Stream %d in unexpected state: %s",
311                             stream.id(), stream.state());
312                 default:
313                     // Connection error.
314                     throw connectionError(PROTOCOL_ERROR, "Stream %d in unexpected state: %s", stream.id(),
315                             stream.state());
316             }
317 
318             stream.headersReceived(isInformational);
319             encoder.flowController().updateDependencyTree(streamId, streamDependency, weight, exclusive);
320 
321             listener.onHeadersRead(ctx, streamId, headers, streamDependency, weight, exclusive, padding, endOfStream);
322 
323             // If the headers completes this stream, close it.
324             if (endOfStream) {
325                 lifecycleManager.closeStreamRemote(stream, ctx.newSucceededFuture());
326             }
327         }
328 
329         @Override
330         public void onPriorityRead(ChannelHandlerContext ctx, int streamId, int streamDependency, short weight,
331                 boolean exclusive) throws Http2Exception {
332             encoder.flowController().updateDependencyTree(streamId, streamDependency, weight, exclusive);
333 
334             listener.onPriorityRead(ctx, streamId, streamDependency, weight, exclusive);
335         }
336 
337         @Override
338         public void onRstStreamRead(ChannelHandlerContext ctx, int streamId, long errorCode) throws Http2Exception {
339             Http2Stream stream = connection.stream(streamId);
340             if (stream == null) {
341                 verifyStreamMayHaveExisted(streamId);
342                 return;
343             }
344 
345             switch(stream.state()) {
346             case IDLE:
347                 throw connectionError(PROTOCOL_ERROR, "RST_STREAM received for IDLE stream %d", streamId);
348             case CLOSED:
349                 return; // RST_STREAM frames must be ignored for closed streams.
350             default:
351                 break;
352             }
353 
354             listener.onRstStreamRead(ctx, streamId, errorCode);
355 
356             lifecycleManager.closeStream(stream, ctx.newSucceededFuture());
357         }
358 
359         @Override
360         public void onSettingsAckRead(ChannelHandlerContext ctx) throws Http2Exception {
361             // Apply oldest outstanding local settings here. This is a synchronization point between endpoints.
362             Http2Settings settings = encoder.pollSentSettings();
363 
364             if (settings != null) {
365                 applyLocalSettings(settings);
366             }
367 
368             listener.onSettingsAckRead(ctx);
369         }
370 
371         /**
372          * Applies settings sent from the local endpoint.
373          * <p>
374          * This method is only called after the local settings have been acknowledged from the remote endpoint.
375          */
376         private void applyLocalSettings(Http2Settings settings) throws Http2Exception {
377             Boolean pushEnabled = settings.pushEnabled();
378             final Http2FrameReader.Configuration config = frameReader.configuration();
379             final Http2HeadersDecoder.Configuration headerConfig = config.headersConfiguration();
380             final Http2FrameSizePolicy frameSizePolicy = config.frameSizePolicy();
381             if (pushEnabled != null) {
382                 if (connection.isServer()) {
383                     throw connectionError(PROTOCOL_ERROR, "Server sending SETTINGS frame with ENABLE_PUSH specified");
384                 }
385                 connection.local().allowPushTo(pushEnabled);
386             }
387 
388             Long maxConcurrentStreams = settings.maxConcurrentStreams();
389             if (maxConcurrentStreams != null) {
390                 connection.remote().maxActiveStreams((int) min(maxConcurrentStreams, MAX_VALUE));
391             }
392 
393             Long headerTableSize = settings.headerTableSize();
394             if (headerTableSize != null) {
395                 headerConfig.maxHeaderTableSize(headerTableSize);
396             }
397 
398             Long maxHeaderListSize = settings.maxHeaderListSize();
399             if (maxHeaderListSize != null) {
400                 headerConfig.maxHeaderListSize(maxHeaderListSize, calculateMaxHeaderListSizeGoAway(maxHeaderListSize));
401             }
402 
403             Integer maxFrameSize = settings.maxFrameSize();
404             if (maxFrameSize != null) {
405                 frameSizePolicy.maxFrameSize(maxFrameSize);
406             }
407 
408             Integer initialWindowSize = settings.initialWindowSize();
409             if (initialWindowSize != null) {
410                 flowController().initialWindowSize(initialWindowSize);
411             }
412         }
413 
414         @Override
415         public void onSettingsRead(ChannelHandlerContext ctx, Http2Settings settings) throws Http2Exception {
416             // Acknowledge receipt of the settings. We should do this before we process the settings to ensure our
417             // remote peer applies these settings before any subsequent frames that we may send which depend upon these
418             // new settings. See https://github.com/netty/netty/issues/6520.
419             encoder.writeSettingsAck(ctx, ctx.newPromise());
420 
421             encoder.remoteSettings(settings);
422 
423             listener.onSettingsRead(ctx, settings);
424         }
425 
426         @Override
427         public void onPingRead(ChannelHandlerContext ctx, long data) throws Http2Exception {
428             // Send an ack back to the remote client.
429             // Need to retain the buffer here since it will be released after the write completes.
430             encoder.writePing(ctx, true, data, ctx.newPromise());
431 
432             listener.onPingRead(ctx, data);
433         }
434 
435         @Override
436         public void onPingAckRead(ChannelHandlerContext ctx, long data) throws Http2Exception {
437             listener.onPingAckRead(ctx, data);
438         }
439 
440         @Override
441         public void onPushPromiseRead(ChannelHandlerContext ctx, int streamId, int promisedStreamId,
442                 Http2Headers headers, int padding) throws Http2Exception {
443             // A client cannot push.
444             if (connection().isServer()) {
445                 throw connectionError(PROTOCOL_ERROR, "A client cannot push.");
446             }
447 
448             Http2Stream parentStream = connection.stream(streamId);
449 
450             if (shouldIgnoreHeadersOrDataFrame(ctx, streamId, parentStream, "PUSH_PROMISE")) {
451                 return;
452             }
453 
454             if (parentStream == null) {
455                 throw connectionError(PROTOCOL_ERROR, "Stream %d does not exist", streamId);
456             }
457 
458             switch (parentStream.state()) {
459               case OPEN:
460               case HALF_CLOSED_LOCAL:
461                   // Allowed to receive push promise in these states.
462                   break;
463               default:
464                   // Connection error.
465                   throw connectionError(PROTOCOL_ERROR,
466                       "Stream %d in unexpected state for receiving push promise: %s",
467                       parentStream.id(), parentStream.state());
468             }
469 
470             if (!requestVerifier.isAuthoritative(ctx, headers)) {
471                 throw streamError(promisedStreamId, PROTOCOL_ERROR,
472                         "Promised request on stream %d for promised stream %d is not authoritative",
473                         streamId, promisedStreamId);
474             }
475             if (!requestVerifier.isCacheable(headers)) {
476                 throw streamError(promisedStreamId, PROTOCOL_ERROR,
477                         "Promised request on stream %d for promised stream %d is not known to be cacheable",
478                         streamId, promisedStreamId);
479             }
480             if (!requestVerifier.isSafe(headers)) {
481                 throw streamError(promisedStreamId, PROTOCOL_ERROR,
482                         "Promised request on stream %d for promised stream %d is not known to be safe",
483                         streamId, promisedStreamId);
484             }
485 
486             // Reserve the push stream based with a priority based on the current stream's priority.
487             connection.remote().reservePushStream(promisedStreamId, parentStream);
488 
489             listener.onPushPromiseRead(ctx, streamId, promisedStreamId, headers, padding);
490         }
491 
492         @Override
493         public void onGoAwayRead(ChannelHandlerContext ctx, int lastStreamId, long errorCode, ByteBuf debugData)
494                 throws Http2Exception {
495             onGoAwayRead0(ctx, lastStreamId, errorCode, debugData);
496         }
497 
498         @Override
499         public void onWindowUpdateRead(ChannelHandlerContext ctx, int streamId, int windowSizeIncrement)
500                 throws Http2Exception {
501             Http2Stream stream = connection.stream(streamId);
502             if (stream == null || stream.state() == CLOSED || streamCreatedAfterGoAwaySent(streamId)) {
503                 // Ignore this frame.
504                 verifyStreamMayHaveExisted(streamId);
505                 return;
506             }
507 
508             // Update the outbound flow control window.
509             encoder.flowController().incrementWindowSize(stream, windowSizeIncrement);
510 
511             listener.onWindowUpdateRead(ctx, streamId, windowSizeIncrement);
512         }
513 
514         @Override
515         public void onUnknownFrame(ChannelHandlerContext ctx, byte frameType, int streamId, Http2Flags flags,
516                 ByteBuf payload) throws Http2Exception {
517             onUnknownFrame0(ctx, frameType, streamId, flags, payload);
518         }
519 
520         /**
521          * Helper method to determine if a frame that has the semantics of headers or data should be ignored for the
522          * {@code stream} (which may be {@code null}) associated with {@code streamId}.
523          */
524         private boolean shouldIgnoreHeadersOrDataFrame(ChannelHandlerContext ctx, int streamId, Http2Stream stream,
525                 String frameName) throws Http2Exception {
526             if (stream == null) {
527                 if (streamCreatedAfterGoAwaySent(streamId)) {
528                     logger.info("{} ignoring {} frame for stream {}. Stream sent after GOAWAY sent",
529                             ctx.channel(), frameName, streamId);
530                     return true;
531                 }
532                 // Its possible that this frame would result in stream ID out of order creation (PROTOCOL ERROR) and its
533                 // also possible that this frame is received on a CLOSED stream (STREAM_CLOSED after a RST_STREAM is
534                 // sent). We don't have enough information to know for sure, so we choose the lesser of the two errors.
535                 throw streamError(streamId, STREAM_CLOSED, "Received %s frame for an unknown stream %d",
536                                   frameName, streamId);
537             } else if (stream.isResetSent() || streamCreatedAfterGoAwaySent(streamId)) {
538                 if (logger.isInfoEnabled()) {
539                     logger.info("{} ignoring {} frame for stream {} {}", ctx.channel(), frameName,
540                             stream.isResetSent() ? "RST_STREAM sent." :
541                                 ("Stream created after GOAWAY sent. Last known stream by peer " +
542                                  connection.remote().lastStreamKnownByPeer()));
543                 }
544                 return true;
545             }
546             return false;
547         }
548 
549         /**
550          * Helper method for determining whether or not to ignore inbound frames. A stream is considered to be created
551          * after a {@code GOAWAY} is sent if the following conditions hold:
552          * <p/>
553          * <ul>
554          *     <li>A {@code GOAWAY} must have been sent by the local endpoint</li>
555          *     <li>The {@code streamId} must identify a legitimate stream id for the remote endpoint to be creating</li>
556          *     <li>{@code streamId} is greater than the Last Known Stream ID which was sent by the local endpoint
557          *     in the last {@code GOAWAY} frame</li>
558          * </ul>
559          * <p/>
560          */
561         private boolean streamCreatedAfterGoAwaySent(int streamId) {
562             Endpoint<?> remote = connection.remote();
563             return connection.goAwaySent() && remote.isValidStreamId(streamId) &&
564                     streamId > remote.lastStreamKnownByPeer();
565         }
566 
567         private void verifyStreamMayHaveExisted(int streamId) throws Http2Exception {
568             if (!connection.streamMayHaveExisted(streamId)) {
569                 throw connectionError(PROTOCOL_ERROR, "Stream %d does not exist", streamId);
570             }
571         }
572     }
573 
574     private final class PrefaceFrameListener implements Http2FrameListener {
575         /**
576          * Verifies that the HTTP/2 connection preface has been received from the remote endpoint.
577          * It is possible that the current call to
578          * {@link Http2FrameReader#readFrame(ChannelHandlerContext, ByteBuf, Http2FrameListener)} will have multiple
579          * frames to dispatch. So it may be OK for this class to get legitimate frames for the first readFrame.
580          */
581         private void verifyPrefaceReceived() throws Http2Exception {
582             if (!prefaceReceived()) {
583                 throw connectionError(PROTOCOL_ERROR, "Received non-SETTINGS as first frame.");
584             }
585         }
586 
587         @Override
588         public int onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, boolean endOfStream)
589                 throws Http2Exception {
590             verifyPrefaceReceived();
591             return internalFrameListener.onDataRead(ctx, streamId, data, padding, endOfStream);
592         }
593 
594         @Override
595         public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int padding,
596                 boolean endOfStream) throws Http2Exception {
597             verifyPrefaceReceived();
598             internalFrameListener.onHeadersRead(ctx, streamId, headers, padding, endOfStream);
599         }
600 
601         @Override
602         public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int streamDependency,
603                 short weight, boolean exclusive, int padding, boolean endOfStream) throws Http2Exception {
604             verifyPrefaceReceived();
605             internalFrameListener.onHeadersRead(ctx, streamId, headers, streamDependency, weight,
606                     exclusive, padding, endOfStream);
607         }
608 
609         @Override
610         public void onPriorityRead(ChannelHandlerContext ctx, int streamId, int streamDependency, short weight,
611                 boolean exclusive) throws Http2Exception {
612             verifyPrefaceReceived();
613             internalFrameListener.onPriorityRead(ctx, streamId, streamDependency, weight, exclusive);
614         }
615 
616         @Override
617         public void onRstStreamRead(ChannelHandlerContext ctx, int streamId, long errorCode) throws Http2Exception {
618             verifyPrefaceReceived();
619             internalFrameListener.onRstStreamRead(ctx, streamId, errorCode);
620         }
621 
622         @Override
623         public void onSettingsAckRead(ChannelHandlerContext ctx) throws Http2Exception {
624             verifyPrefaceReceived();
625             internalFrameListener.onSettingsAckRead(ctx);
626         }
627 
628         @Override
629         public void onSettingsRead(ChannelHandlerContext ctx, Http2Settings settings) throws Http2Exception {
630             // The first settings should change the internalFrameListener to the "real" listener
631             // that expects the preface to be verified.
632             if (!prefaceReceived()) {
633                 internalFrameListener = new FrameReadListener();
634             }
635             internalFrameListener.onSettingsRead(ctx, settings);
636         }
637 
638         @Override
639         public void onPingRead(ChannelHandlerContext ctx, long data) throws Http2Exception {
640             verifyPrefaceReceived();
641             internalFrameListener.onPingRead(ctx, data);
642         }
643 
644         @Override
645         public void onPingAckRead(ChannelHandlerContext ctx, long data) throws Http2Exception {
646             verifyPrefaceReceived();
647             internalFrameListener.onPingAckRead(ctx, data);
648         }
649 
650         @Override
651         public void onPushPromiseRead(ChannelHandlerContext ctx, int streamId, int promisedStreamId,
652                 Http2Headers headers, int padding) throws Http2Exception {
653             verifyPrefaceReceived();
654             internalFrameListener.onPushPromiseRead(ctx, streamId, promisedStreamId, headers, padding);
655         }
656 
657         @Override
658         public void onGoAwayRead(ChannelHandlerContext ctx, int lastStreamId, long errorCode, ByteBuf debugData)
659                 throws Http2Exception {
660             onGoAwayRead0(ctx, lastStreamId, errorCode, debugData);
661         }
662 
663         @Override
664         public void onWindowUpdateRead(ChannelHandlerContext ctx, int streamId, int windowSizeIncrement)
665                 throws Http2Exception {
666             verifyPrefaceReceived();
667             internalFrameListener.onWindowUpdateRead(ctx, streamId, windowSizeIncrement);
668         }
669 
670         @Override
671         public void onUnknownFrame(ChannelHandlerContext ctx, byte frameType, int streamId, Http2Flags flags,
672                 ByteBuf payload) throws Http2Exception {
673             onUnknownFrame0(ctx, frameType, streamId, flags, payload);
674         }
675     }
676 }