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