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.buffer.ByteBufAllocator;
19  import io.netty.channel.ChannelHandlerContext;
20  import io.netty.handler.codec.http2.Http2FrameReader.Configuration;
21  import io.netty.util.internal.PlatformDependent;
22  import io.netty.util.internal.UnstableApi;
23  
24  import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_MAX_FRAME_SIZE;
25  import static io.netty.handler.codec.http2.Http2CodecUtil.FRAME_HEADER_LENGTH;
26  import static io.netty.handler.codec.http2.Http2CodecUtil.INT_FIELD_LENGTH;
27  import static io.netty.handler.codec.http2.Http2CodecUtil.PING_FRAME_PAYLOAD_LENGTH;
28  import static io.netty.handler.codec.http2.Http2CodecUtil.PRIORITY_ENTRY_LENGTH;
29  import static io.netty.handler.codec.http2.Http2CodecUtil.SETTINGS_INITIAL_WINDOW_SIZE;
30  import static io.netty.handler.codec.http2.Http2CodecUtil.SETTINGS_MAX_FRAME_SIZE;
31  import static io.netty.handler.codec.http2.Http2CodecUtil.SETTING_ENTRY_LENGTH;
32  import static io.netty.handler.codec.http2.Http2CodecUtil.headerListSizeExceeded;
33  import static io.netty.handler.codec.http2.Http2CodecUtil.isMaxFrameSizeValid;
34  import static io.netty.handler.codec.http2.Http2CodecUtil.readUnsignedInt;
35  import static io.netty.handler.codec.http2.Http2Error.FLOW_CONTROL_ERROR;
36  import static io.netty.handler.codec.http2.Http2Error.FRAME_SIZE_ERROR;
37  import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
38  import static io.netty.handler.codec.http2.Http2Exception.connectionError;
39  import static io.netty.handler.codec.http2.Http2Exception.streamError;
40  import static io.netty.handler.codec.http2.Http2FrameTypes.CONTINUATION;
41  import static io.netty.handler.codec.http2.Http2FrameTypes.DATA;
42  import static io.netty.handler.codec.http2.Http2FrameTypes.GO_AWAY;
43  import static io.netty.handler.codec.http2.Http2FrameTypes.HEADERS;
44  import static io.netty.handler.codec.http2.Http2FrameTypes.PING;
45  import static io.netty.handler.codec.http2.Http2FrameTypes.PRIORITY;
46  import static io.netty.handler.codec.http2.Http2FrameTypes.PUSH_PROMISE;
47  import static io.netty.handler.codec.http2.Http2FrameTypes.RST_STREAM;
48  import static io.netty.handler.codec.http2.Http2FrameTypes.SETTINGS;
49  import static io.netty.handler.codec.http2.Http2FrameTypes.WINDOW_UPDATE;
50  
51  /**
52   * A {@link Http2FrameReader} that supports all frame types defined by the HTTP/2 specification.
53   */
54  @UnstableApi
55  public class DefaultHttp2FrameReader implements Http2FrameReader, Http2FrameSizePolicy, Configuration {
56      private final Http2HeadersDecoder headersDecoder;
57  
58      /**
59       * {@code true} = reading headers, {@code false} = reading payload.
60       */
61      private boolean readingHeaders = true;
62      /**
63       * Once set to {@code true} the value will never change. This is set to {@code true} if an unrecoverable error which
64       * renders the connection unusable.
65       */
66      private boolean readError;
67      private byte frameType;
68      private int streamId;
69      private Http2Flags flags;
70      private int payloadLength;
71      private HeadersContinuation headersContinuation;
72      private int maxFrameSize;
73  
74      /**
75       * Create a new instance.
76       * <p>
77       * Header names will be validated.
78       */
79      public DefaultHttp2FrameReader() {
80          this(true);
81      }
82  
83      /**
84       * Create a new instance.
85       * @param validateHeaders {@code true} to validate headers. {@code false} to not validate headers.
86       * @see DefaultHttp2HeadersDecoder(boolean)
87       */
88      public DefaultHttp2FrameReader(boolean validateHeaders) {
89          this(new DefaultHttp2HeadersDecoder(validateHeaders));
90      }
91  
92      public DefaultHttp2FrameReader(Http2HeadersDecoder headersDecoder) {
93          this.headersDecoder = headersDecoder;
94          maxFrameSize = DEFAULT_MAX_FRAME_SIZE;
95      }
96  
97      @Override
98      public Http2HeadersDecoder.Configuration headersConfiguration() {
99          return headersDecoder.configuration();
100     }
101 
102     @Override
103     public Configuration configuration() {
104         return this;
105     }
106 
107     @Override
108     public Http2FrameSizePolicy frameSizePolicy() {
109         return this;
110     }
111 
112     @Override
113     public void maxFrameSize(int max) throws Http2Exception {
114         if (!isMaxFrameSizeValid(max)) {
115             throw streamError(streamId, FRAME_SIZE_ERROR,
116                     "Invalid MAX_FRAME_SIZE specified in sent settings: %d", max);
117         }
118         maxFrameSize = max;
119     }
120 
121     @Override
122     public int maxFrameSize() {
123         return maxFrameSize;
124     }
125 
126     @Override
127     public void close() {
128         closeHeadersContinuation();
129     }
130 
131     private void closeHeadersContinuation() {
132         if (headersContinuation != null) {
133             headersContinuation.close();
134             headersContinuation = null;
135         }
136     }
137 
138     @Override
139     public void readFrame(ChannelHandlerContext ctx, ByteBuf input, Http2FrameListener listener)
140             throws Http2Exception {
141         if (readError) {
142             input.skipBytes(input.readableBytes());
143             return;
144         }
145         try {
146             do {
147                 if (readingHeaders) {
148                     processHeaderState(input);
149                     if (readingHeaders) {
150                         // Wait until the entire header has arrived.
151                         return;
152                     }
153                 }
154 
155                 // The header is complete, fall into the next case to process the payload.
156                 // This is to ensure the proper handling of zero-length payloads. In this
157                 // case, we don't want to loop around because there may be no more data
158                 // available, causing us to exit the loop. Instead, we just want to perform
159                 // the first pass at payload processing now.
160                 processPayloadState(ctx, input, listener);
161                 if (!readingHeaders) {
162                     // Wait until the entire payload has arrived.
163                     return;
164                 }
165             } while (input.isReadable());
166         } catch (Http2Exception e) {
167             readError = !Http2Exception.isStreamError(e);
168             throw e;
169         } catch (RuntimeException e) {
170             readError = true;
171             throw e;
172         } catch (Throwable cause) {
173             readError = true;
174             PlatformDependent.throwException(cause);
175         }
176     }
177 
178     private void processHeaderState(ByteBuf in) throws Http2Exception {
179         if (in.readableBytes() < FRAME_HEADER_LENGTH) {
180             // Wait until the entire frame header has been read.
181             return;
182         }
183 
184         // Read the header and prepare the unmarshaller to read the frame.
185         payloadLength = in.readUnsignedMedium();
186         if (payloadLength > maxFrameSize) {
187             throw connectionError(FRAME_SIZE_ERROR, "Frame length: %d exceeds maximum: %d", payloadLength,
188                                   maxFrameSize);
189         }
190         frameType = in.readByte();
191         flags = new Http2Flags(in.readUnsignedByte());
192         streamId = readUnsignedInt(in);
193 
194         // We have consumed the data, next time we read we will be expecting to read the frame payload.
195         readingHeaders = false;
196 
197         switch (frameType) {
198             case DATA:
199                 verifyDataFrame();
200                 break;
201             case HEADERS:
202                 verifyHeadersFrame();
203                 break;
204             case PRIORITY:
205                 verifyPriorityFrame();
206                 break;
207             case RST_STREAM:
208                 verifyRstStreamFrame();
209                 break;
210             case SETTINGS:
211                 verifySettingsFrame();
212                 break;
213             case PUSH_PROMISE:
214                 verifyPushPromiseFrame();
215                 break;
216             case PING:
217                 verifyPingFrame();
218                 break;
219             case GO_AWAY:
220                 verifyGoAwayFrame();
221                 break;
222             case WINDOW_UPDATE:
223                 verifyWindowUpdateFrame();
224                 break;
225             case CONTINUATION:
226                 verifyContinuationFrame();
227                 break;
228             default:
229                 // Unknown frame type, could be an extension.
230                 verifyUnknownFrame();
231                 break;
232         }
233     }
234 
235     private void processPayloadState(ChannelHandlerContext ctx, ByteBuf in, Http2FrameListener listener)
236                     throws Http2Exception {
237         if (in.readableBytes() < payloadLength) {
238             // Wait until the entire payload has been read.
239             return;
240         }
241 
242         // Get a view of the buffer for the size of the payload.
243         ByteBuf payload = in.readSlice(payloadLength);
244 
245         // We have consumed the data, next time we read we will be expecting to read a frame header.
246         readingHeaders = true;
247 
248         // Read the payload and fire the frame event to the listener.
249         switch (frameType) {
250             case DATA:
251                 readDataFrame(ctx, payload, listener);
252                 break;
253             case HEADERS:
254                 readHeadersFrame(ctx, payload, listener);
255                 break;
256             case PRIORITY:
257                 readPriorityFrame(ctx, payload, listener);
258                 break;
259             case RST_STREAM:
260                 readRstStreamFrame(ctx, payload, listener);
261                 break;
262             case SETTINGS:
263                 readSettingsFrame(ctx, payload, listener);
264                 break;
265             case PUSH_PROMISE:
266                 readPushPromiseFrame(ctx, payload, listener);
267                 break;
268             case PING:
269                 readPingFrame(ctx, payload.readLong(), listener);
270                 break;
271             case GO_AWAY:
272                 readGoAwayFrame(ctx, payload, listener);
273                 break;
274             case WINDOW_UPDATE:
275                 readWindowUpdateFrame(ctx, payload, listener);
276                 break;
277             case CONTINUATION:
278                 readContinuationFrame(payload, listener);
279                 break;
280             default:
281                 readUnknownFrame(ctx, payload, listener);
282                 break;
283         }
284     }
285 
286     private void verifyDataFrame() throws Http2Exception {
287         verifyAssociatedWithAStream();
288         verifyNotProcessingHeaders();
289         verifyPayloadLength(payloadLength);
290 
291         if (payloadLength < flags.getPaddingPresenceFieldLength()) {
292             throw streamError(streamId, FRAME_SIZE_ERROR,
293                     "Frame length %d too small.", payloadLength);
294         }
295     }
296 
297     private void verifyHeadersFrame() throws Http2Exception {
298         verifyAssociatedWithAStream();
299         verifyNotProcessingHeaders();
300         verifyPayloadLength(payloadLength);
301 
302         int requiredLength = flags.getPaddingPresenceFieldLength() + flags.getNumPriorityBytes();
303         if (payloadLength < requiredLength) {
304             throw streamError(streamId, FRAME_SIZE_ERROR,
305                     "Frame length too small." + payloadLength);
306         }
307     }
308 
309     private void verifyPriorityFrame() throws Http2Exception {
310         verifyAssociatedWithAStream();
311         verifyNotProcessingHeaders();
312 
313         if (payloadLength != PRIORITY_ENTRY_LENGTH) {
314             throw streamError(streamId, FRAME_SIZE_ERROR,
315                     "Invalid frame length %d.", payloadLength);
316         }
317     }
318 
319     private void verifyRstStreamFrame() throws Http2Exception {
320         verifyAssociatedWithAStream();
321         verifyNotProcessingHeaders();
322 
323         if (payloadLength != INT_FIELD_LENGTH) {
324             throw connectionError(FRAME_SIZE_ERROR, "Invalid frame length %d.", payloadLength);
325         }
326     }
327 
328     private void verifySettingsFrame() throws Http2Exception {
329         verifyNotProcessingHeaders();
330         verifyPayloadLength(payloadLength);
331         if (streamId != 0) {
332             throw connectionError(PROTOCOL_ERROR, "A stream ID must be zero.");
333         }
334         if (flags.ack() && payloadLength > 0) {
335             throw connectionError(FRAME_SIZE_ERROR, "Ack settings frame must have an empty payload.");
336         }
337         if (payloadLength % SETTING_ENTRY_LENGTH > 0) {
338             throw connectionError(FRAME_SIZE_ERROR, "Frame length %d invalid.", payloadLength);
339         }
340     }
341 
342     private void verifyPushPromiseFrame() throws Http2Exception {
343         verifyNotProcessingHeaders();
344         verifyPayloadLength(payloadLength);
345 
346         // Subtract the length of the promised stream ID field, to determine the length of the
347         // rest of the payload (header block fragment + payload).
348         int minLength = flags.getPaddingPresenceFieldLength() + INT_FIELD_LENGTH;
349         if (payloadLength < minLength) {
350             throw streamError(streamId, FRAME_SIZE_ERROR,
351                     "Frame length %d too small.", payloadLength);
352         }
353     }
354 
355     private void verifyPingFrame() throws Http2Exception {
356         verifyNotProcessingHeaders();
357         if (streamId != 0) {
358             throw connectionError(PROTOCOL_ERROR, "A stream ID must be zero.");
359         }
360         if (payloadLength != PING_FRAME_PAYLOAD_LENGTH) {
361             throw connectionError(FRAME_SIZE_ERROR,
362                     "Frame length %d incorrect size for ping.", payloadLength);
363         }
364     }
365 
366     private void verifyGoAwayFrame() throws Http2Exception {
367         verifyNotProcessingHeaders();
368         verifyPayloadLength(payloadLength);
369 
370         if (streamId != 0) {
371             throw connectionError(PROTOCOL_ERROR, "A stream ID must be zero.");
372         }
373         if (payloadLength < 8) {
374             throw connectionError(FRAME_SIZE_ERROR, "Frame length %d too small.", payloadLength);
375         }
376     }
377 
378     private void verifyWindowUpdateFrame() throws Http2Exception {
379         verifyNotProcessingHeaders();
380         verifyStreamOrConnectionId(streamId, "Stream ID");
381 
382         if (payloadLength != INT_FIELD_LENGTH) {
383             throw connectionError(FRAME_SIZE_ERROR, "Invalid frame length %d.", payloadLength);
384         }
385     }
386 
387     private void verifyContinuationFrame() throws Http2Exception {
388         verifyAssociatedWithAStream();
389         verifyPayloadLength(payloadLength);
390 
391         if (headersContinuation == null) {
392             throw connectionError(PROTOCOL_ERROR, "Received %s frame but not currently processing headers.",
393                     frameType);
394         }
395 
396         if (streamId != headersContinuation.getStreamId()) {
397             throw connectionError(PROTOCOL_ERROR, "Continuation stream ID does not match pending headers. "
398                     + "Expected %d, but received %d.", headersContinuation.getStreamId(), streamId);
399         }
400 
401         if (payloadLength < flags.getPaddingPresenceFieldLength()) {
402             throw streamError(streamId, FRAME_SIZE_ERROR,
403                     "Frame length %d too small for padding.", payloadLength);
404         }
405     }
406 
407     private void verifyUnknownFrame() throws Http2Exception {
408         verifyNotProcessingHeaders();
409     }
410 
411     private void readDataFrame(ChannelHandlerContext ctx, ByteBuf payload,
412             Http2FrameListener listener) throws Http2Exception {
413         int padding = readPadding(payload);
414         verifyPadding(padding);
415 
416         // Determine how much data there is to read by removing the trailing
417         // padding.
418         int dataLength = lengthWithoutTrailingPadding(payload.readableBytes(), padding);
419 
420         ByteBuf data = payload.readSlice(dataLength);
421         listener.onDataRead(ctx, streamId, data, padding, flags.endOfStream());
422         payload.skipBytes(payload.readableBytes());
423     }
424 
425     private void readHeadersFrame(final ChannelHandlerContext ctx, ByteBuf payload,
426             Http2FrameListener listener) throws Http2Exception {
427         final int headersStreamId = streamId;
428         final Http2Flags headersFlags = flags;
429         final int padding = readPadding(payload);
430         verifyPadding(padding);
431 
432         // The callback that is invoked is different depending on whether priority information
433         // is present in the headers frame.
434         if (flags.priorityPresent()) {
435             long word1 = payload.readUnsignedInt();
436             final boolean exclusive = (word1 & 0x80000000L) != 0;
437             final int streamDependency = (int) (word1 & 0x7FFFFFFFL);
438             if (streamDependency == streamId) {
439                 throw streamError(streamId, PROTOCOL_ERROR, "A stream cannot depend on itself.");
440             }
441             final short weight = (short) (payload.readUnsignedByte() + 1);
442             final ByteBuf fragment = payload.readSlice(lengthWithoutTrailingPadding(payload.readableBytes(), padding));
443 
444             // Create a handler that invokes the listener when the header block is complete.
445             headersContinuation = new HeadersContinuation() {
446                 @Override
447                 public int getStreamId() {
448                     return headersStreamId;
449                 }
450 
451                 @Override
452                 public void processFragment(boolean endOfHeaders, ByteBuf fragment,
453                         Http2FrameListener listener) throws Http2Exception {
454                     final HeadersBlockBuilder hdrBlockBuilder = headersBlockBuilder();
455                     hdrBlockBuilder.addFragment(fragment, ctx.alloc(), endOfHeaders);
456                     if (endOfHeaders) {
457                         listener.onHeadersRead(ctx, headersStreamId, hdrBlockBuilder.headers(), streamDependency,
458                                 weight, exclusive, padding, headersFlags.endOfStream());
459                     }
460                 }
461             };
462 
463             // Process the initial fragment, invoking the listener's callback if end of headers.
464             headersContinuation.processFragment(flags.endOfHeaders(), fragment, listener);
465             resetHeadersContinuationIfEnd(flags.endOfHeaders());
466             return;
467         }
468 
469         // The priority fields are not present in the frame. Prepare a continuation that invokes
470         // the listener callback without priority information.
471         headersContinuation = new HeadersContinuation() {
472             @Override
473             public int getStreamId() {
474                 return headersStreamId;
475             }
476 
477             @Override
478             public void processFragment(boolean endOfHeaders, ByteBuf fragment,
479                     Http2FrameListener listener) throws Http2Exception {
480                 final HeadersBlockBuilder hdrBlockBuilder = headersBlockBuilder();
481                 hdrBlockBuilder.addFragment(fragment, ctx.alloc(), endOfHeaders);
482                 if (endOfHeaders) {
483                     listener.onHeadersRead(ctx, headersStreamId, hdrBlockBuilder.headers(), padding,
484                                     headersFlags.endOfStream());
485                 }
486             }
487         };
488 
489         // Process the initial fragment, invoking the listener's callback if end of headers.
490         final ByteBuf fragment = payload.readSlice(lengthWithoutTrailingPadding(payload.readableBytes(), padding));
491         headersContinuation.processFragment(flags.endOfHeaders(), fragment, listener);
492         resetHeadersContinuationIfEnd(flags.endOfHeaders());
493     }
494 
495     private void resetHeadersContinuationIfEnd(boolean endOfHeaders) {
496         if (endOfHeaders) {
497             closeHeadersContinuation();
498         }
499     }
500 
501     private void readPriorityFrame(ChannelHandlerContext ctx, ByteBuf payload,
502             Http2FrameListener listener) throws Http2Exception {
503         long word1 = payload.readUnsignedInt();
504         boolean exclusive = (word1 & 0x80000000L) != 0;
505         int streamDependency = (int) (word1 & 0x7FFFFFFFL);
506         if (streamDependency == streamId) {
507             throw streamError(streamId, PROTOCOL_ERROR, "A stream cannot depend on itself.");
508         }
509         short weight = (short) (payload.readUnsignedByte() + 1);
510         listener.onPriorityRead(ctx, streamId, streamDependency, weight, exclusive);
511     }
512 
513     private void readRstStreamFrame(ChannelHandlerContext ctx, ByteBuf payload,
514             Http2FrameListener listener) throws Http2Exception {
515         long errorCode = payload.readUnsignedInt();
516         listener.onRstStreamRead(ctx, streamId, errorCode);
517     }
518 
519     private void readSettingsFrame(ChannelHandlerContext ctx, ByteBuf payload,
520             Http2FrameListener listener) throws Http2Exception {
521         if (flags.ack()) {
522             listener.onSettingsAckRead(ctx);
523         } else {
524             int numSettings = payloadLength / SETTING_ENTRY_LENGTH;
525             Http2Settings settings = new Http2Settings();
526             for (int index = 0; index < numSettings; ++index) {
527                 char id = (char) payload.readUnsignedShort();
528                 long value = payload.readUnsignedInt();
529                 try {
530                     settings.put(id, Long.valueOf(value));
531                 } catch (IllegalArgumentException e) {
532                     switch(id) {
533                     case SETTINGS_MAX_FRAME_SIZE:
534                         throw connectionError(PROTOCOL_ERROR, e, e.getMessage());
535                     case SETTINGS_INITIAL_WINDOW_SIZE:
536                         throw connectionError(FLOW_CONTROL_ERROR, e, e.getMessage());
537                     default:
538                         throw connectionError(PROTOCOL_ERROR, e, e.getMessage());
539                     }
540                 }
541             }
542             listener.onSettingsRead(ctx, settings);
543         }
544     }
545 
546     private void readPushPromiseFrame(final ChannelHandlerContext ctx, ByteBuf payload,
547             Http2FrameListener listener) throws Http2Exception {
548         final int pushPromiseStreamId = streamId;
549         final int padding = readPadding(payload);
550         verifyPadding(padding);
551         final int promisedStreamId = readUnsignedInt(payload);
552 
553         // Create a handler that invokes the listener when the header block is complete.
554         headersContinuation = new HeadersContinuation() {
555             @Override
556             public int getStreamId() {
557                 return pushPromiseStreamId;
558             }
559 
560             @Override
561             public void processFragment(boolean endOfHeaders, ByteBuf fragment,
562                     Http2FrameListener listener) throws Http2Exception {
563                 headersBlockBuilder().addFragment(fragment, ctx.alloc(), endOfHeaders);
564                 if (endOfHeaders) {
565                     listener.onPushPromiseRead(ctx, pushPromiseStreamId, promisedStreamId,
566                             headersBlockBuilder().headers(), padding);
567                 }
568             }
569         };
570 
571         // Process the initial fragment, invoking the listener's callback if end of headers.
572         final ByteBuf fragment = payload.readSlice(lengthWithoutTrailingPadding(payload.readableBytes(), padding));
573         headersContinuation.processFragment(flags.endOfHeaders(), fragment, listener);
574         resetHeadersContinuationIfEnd(flags.endOfHeaders());
575     }
576 
577     private void readPingFrame(ChannelHandlerContext ctx, long data,
578             Http2FrameListener listener) throws Http2Exception {
579         if (flags.ack()) {
580             listener.onPingAckRead(ctx, data);
581         } else {
582             listener.onPingRead(ctx, data);
583         }
584     }
585 
586     private static void readGoAwayFrame(ChannelHandlerContext ctx, ByteBuf payload,
587             Http2FrameListener listener) throws Http2Exception {
588         int lastStreamId = readUnsignedInt(payload);
589         long errorCode = payload.readUnsignedInt();
590         ByteBuf debugData = payload.readSlice(payload.readableBytes());
591         listener.onGoAwayRead(ctx, lastStreamId, errorCode, debugData);
592     }
593 
594     private void readWindowUpdateFrame(ChannelHandlerContext ctx, ByteBuf payload,
595             Http2FrameListener listener) throws Http2Exception {
596         int windowSizeIncrement = readUnsignedInt(payload);
597         if (windowSizeIncrement == 0) {
598             throw streamError(streamId, PROTOCOL_ERROR,
599                     "Received WINDOW_UPDATE with delta 0 for stream: %d", streamId);
600         }
601         listener.onWindowUpdateRead(ctx, streamId, windowSizeIncrement);
602     }
603 
604     private void readContinuationFrame(ByteBuf payload, Http2FrameListener listener)
605             throws Http2Exception {
606         // Process the initial fragment, invoking the listener's callback if end of headers.
607         final ByteBuf continuationFragment = payload.readSlice(payload.readableBytes());
608         headersContinuation.processFragment(flags.endOfHeaders(), continuationFragment,
609                 listener);
610         resetHeadersContinuationIfEnd(flags.endOfHeaders());
611     }
612 
613     private void readUnknownFrame(ChannelHandlerContext ctx, ByteBuf payload, Http2FrameListener listener)
614             throws Http2Exception {
615         payload = payload.readSlice(payload.readableBytes());
616         listener.onUnknownFrame(ctx, frameType, streamId, flags, payload);
617     }
618 
619     /**
620      * If padding is present in the payload, reads the next byte as padding. The padding also includes the one byte
621      * width of the pad length field. Otherwise, returns zero.
622      */
623     private int readPadding(ByteBuf payload) {
624         if (!flags.paddingPresent()) {
625             return 0;
626         }
627         return payload.readUnsignedByte() + 1;
628     }
629 
630     private void verifyPadding(int padding) throws Http2Exception {
631         int len = lengthWithoutTrailingPadding(payloadLength, padding);
632         if (len < 0) {
633             throw connectionError(PROTOCOL_ERROR, "Frame payload too small for padding.");
634         }
635     }
636 
637     /**
638      * The padding parameter consists of the 1 byte pad length field and the trailing padding bytes. This method
639      * returns the number of readable bytes without the trailing padding.
640      */
641     private static int lengthWithoutTrailingPadding(int readableBytes, int padding) {
642         return padding == 0
643                 ? readableBytes
644                 : readableBytes - (padding - 1);
645     }
646 
647     /**
648      * Base class for processing of HEADERS and PUSH_PROMISE header blocks that potentially span
649      * multiple frames. The implementation of this interface will perform the final callback to the
650      * {@link Http2FrameListener} once the end of headers is reached.
651      */
652     private abstract class HeadersContinuation {
653         private final HeadersBlockBuilder builder = new HeadersBlockBuilder();
654 
655         /**
656          * Returns the stream for which headers are currently being processed.
657          */
658         abstract int getStreamId();
659 
660         /**
661          * Processes the next fragment for the current header block.
662          *
663          * @param endOfHeaders whether the fragment is the last in the header block.
664          * @param fragment the fragment of the header block to be added.
665          * @param listener the listener to be notified if the header block is completed.
666          */
667         abstract void processFragment(boolean endOfHeaders, ByteBuf fragment,
668                 Http2FrameListener listener) throws Http2Exception;
669 
670         final HeadersBlockBuilder headersBlockBuilder() {
671             return builder;
672         }
673 
674         /**
675          * Free any allocated resources.
676          */
677         final void close() {
678             builder.close();
679         }
680     }
681 
682     /**
683      * Utility class to help with construction of the headers block that may potentially span
684      * multiple frames.
685      */
686     protected class HeadersBlockBuilder {
687         private ByteBuf headerBlock;
688 
689         /**
690          * The local header size maximum has been exceeded while accumulating bytes.
691          * @throws Http2Exception A connection error indicating too much data has been received.
692          */
693         private void headerSizeExceeded() throws Http2Exception {
694             close();
695             headerListSizeExceeded(headersDecoder.configuration().maxHeaderListSizeGoAway());
696         }
697 
698         /**
699          * Adds a fragment to the block.
700          *
701          * @param fragment the fragment of the headers block to be added.
702          * @param alloc allocator for new blocks if needed.
703          * @param endOfHeaders flag indicating whether the current frame is the end of the headers.
704          *            This is used for an optimization for when the first fragment is the full
705          *            block. In that case, the buffer is used directly without copying.
706          */
707         final void addFragment(ByteBuf fragment, ByteBufAllocator alloc, boolean endOfHeaders) throws Http2Exception {
708             if (headerBlock == null) {
709                 if (fragment.readableBytes() > headersDecoder.configuration().maxHeaderListSizeGoAway()) {
710                     headerSizeExceeded();
711                 }
712                 if (endOfHeaders) {
713                     // Optimization - don't bother copying, just use the buffer as-is. Need
714                     // to retain since we release when the header block is built.
715                     headerBlock = fragment.retain();
716                 } else {
717                     headerBlock = alloc.buffer(fragment.readableBytes());
718                     headerBlock.writeBytes(fragment);
719                 }
720                 return;
721             }
722             if (headersDecoder.configuration().maxHeaderListSizeGoAway() - fragment.readableBytes() <
723                     headerBlock.readableBytes()) {
724                 headerSizeExceeded();
725             }
726             if (headerBlock.isWritable(fragment.readableBytes())) {
727                 // The buffer can hold the requested bytes, just write it directly.
728                 headerBlock.writeBytes(fragment);
729             } else {
730                 // Allocate a new buffer that is big enough to hold the entire header block so far.
731                 ByteBuf buf = alloc.buffer(headerBlock.readableBytes() + fragment.readableBytes());
732                 buf.writeBytes(headerBlock);
733                 buf.writeBytes(fragment);
734                 headerBlock.release();
735                 headerBlock = buf;
736             }
737         }
738 
739         /**
740          * Builds the headers from the completed headers block. After this is called, this builder
741          * should not be called again.
742          */
743         Http2Headers headers() throws Http2Exception {
744             try {
745                 return headersDecoder.decodeHeaders(streamId, headerBlock);
746             } finally {
747                 close();
748             }
749         }
750 
751         /**
752          * Closes this builder and frees any resources.
753          */
754         void close() {
755             if (headerBlock != null) {
756                 headerBlock.release();
757                 headerBlock = null;
758             }
759 
760             // Clear the member variable pointing at this instance.
761             headersContinuation = null;
762         }
763     }
764 
765     /**
766      * Verify that current state is not processing on header block
767      * @throws Http2Exception thrown if {@link #headersContinuation} is not null
768      */
769     private void verifyNotProcessingHeaders() throws Http2Exception {
770         if (headersContinuation != null) {
771             throw connectionError(PROTOCOL_ERROR, "Received frame of type %s while processing headers on stream %d.",
772                                   frameType, headersContinuation.getStreamId());
773         }
774     }
775 
776     private void verifyPayloadLength(int payloadLength) throws Http2Exception {
777         if (payloadLength > maxFrameSize) {
778             throw connectionError(PROTOCOL_ERROR, "Total payload length %d exceeds max frame length.", payloadLength);
779         }
780     }
781 
782     private void verifyAssociatedWithAStream() throws Http2Exception {
783         if (streamId == 0) {
784             throw connectionError(PROTOCOL_ERROR, "Frame of type %s must be associated with a stream.", frameType);
785         }
786     }
787 
788     private static void verifyStreamOrConnectionId(int streamId, String argumentName)
789             throws Http2Exception {
790         if (streamId < 0) {
791             throw connectionError(PROTOCOL_ERROR, "%s must be >= 0", argumentName);
792         }
793     }
794 }