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