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