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         verifyPadding(padding);
403 
404         // Determine how much data there is to read by removing the trailing
405         // padding.
406         int dataLength = lengthWithoutTrailingPadding(payload.readableBytes(), padding);
407 
408         payload.writerIndex(payload.readerIndex() + dataLength);
409         listener.onDataRead(ctx, streamId, payload, padding, flags.endOfStream());
410     }
411 
412     private void readHeadersFrame(final ChannelHandlerContext ctx, ByteBuf payload,
413             Http2FrameListener listener) throws Http2Exception {
414         final int headersStreamId = streamId;
415         final Http2Flags headersFlags = flags;
416         final int padding = readPadding(payload);
417         verifyPadding(padding);
418 
419         // The callback that is invoked is different depending on whether priority information
420         // is present in the headers frame.
421         if (flags.priorityPresent()) {
422             long word1 = payload.readUnsignedInt();
423             final boolean exclusive = (word1 & 0x80000000L) != 0;
424             final int streamDependency = (int) (word1 & 0x7FFFFFFFL);
425             if (streamDependency == streamId) {
426                 // Stream dependencies are deprecated in RFC 9113 but this behavior is defined in
427                 // https://datatracker.ietf.org/doc/html/rfc7540#section-5.3.1 which says this must be treated as a
428                 // stream error of type PROTOCOL_ERROR. However, because we will not process the payload, a stream
429                 // error would result in HPACK corruption. Therefor, it is elevated to a connection error.
430                 throw connectionError(
431                         PROTOCOL_ERROR, "HEADERS frame for stream %d cannot depend on itself.", streamId);
432             }
433             final short weight = (short) (payload.readUnsignedByte() + 1);
434             final int lenToRead = lengthWithoutTrailingPadding(payload.readableBytes(), padding);
435 
436             // Create a handler that invokes the listener when the header block is complete.
437             headersContinuation = new HeadersContinuation() {
438                 @Override
439                 public int getStreamId() {
440                     return headersStreamId;
441                 }
442 
443                 @Override
444                 public void processFragment(boolean endOfHeaders, ByteBuf fragment, int len,
445                         Http2FrameListener listener) throws Http2Exception {
446                     final HeadersBlockBuilder hdrBlockBuilder = headersBlockBuilder();
447                     hdrBlockBuilder.addFragment(fragment, len, ctx.alloc(), endOfHeaders);
448                     if (endOfHeaders) {
449                         listener.onHeadersRead(ctx, headersStreamId, hdrBlockBuilder.headers(), streamDependency,
450                                 weight, exclusive, padding, headersFlags.endOfStream());
451                     }
452                 }
453             };
454 
455             // Process the initial fragment, invoking the listener's callback if end of headers.
456             headersContinuation.processFragment(flags.endOfHeaders(), payload, lenToRead, listener);
457             resetHeadersContinuationIfEnd(flags.endOfHeaders());
458             return;
459         }
460 
461         // The priority fields are not present in the frame. Prepare a continuation that invokes
462         // the listener callback without priority information.
463         headersContinuation = new HeadersContinuation() {
464             @Override
465             public int getStreamId() {
466                 return headersStreamId;
467             }
468 
469             @Override
470             public void processFragment(boolean endOfHeaders, ByteBuf fragment, int len,
471                     Http2FrameListener listener) throws Http2Exception {
472                 final HeadersBlockBuilder hdrBlockBuilder = headersBlockBuilder();
473                 hdrBlockBuilder.addFragment(fragment, len, ctx.alloc(), endOfHeaders);
474                 if (endOfHeaders) {
475                     listener.onHeadersRead(ctx, headersStreamId, hdrBlockBuilder.headers(), padding,
476                                     headersFlags.endOfStream());
477                 }
478             }
479         };
480 
481         // Process the initial fragment, invoking the listener's callback if end of headers.
482         int len = lengthWithoutTrailingPadding(payload.readableBytes(), padding);
483         headersContinuation.processFragment(flags.endOfHeaders(), payload, len, listener);
484         resetHeadersContinuationIfEnd(flags.endOfHeaders());
485     }
486 
487     private void resetHeadersContinuationIfEnd(boolean endOfHeaders) {
488         if (endOfHeaders) {
489             closeHeadersContinuation();
490         }
491     }
492 
493     private void readPriorityFrame(ChannelHandlerContext ctx, ByteBuf payload,
494             Http2FrameListener listener) throws Http2Exception {
495         long word1 = payload.readUnsignedInt();
496         boolean exclusive = (word1 & 0x80000000L) != 0;
497         int streamDependency = (int) (word1 & 0x7FFFFFFFL);
498         if (streamDependency == streamId) {
499             throw streamError(streamId, PROTOCOL_ERROR, "A stream cannot depend on itself.");
500         }
501         short weight = (short) (payload.readUnsignedByte() + 1);
502         listener.onPriorityRead(ctx, streamId, streamDependency, weight, exclusive);
503     }
504 
505     private void readRstStreamFrame(ChannelHandlerContext ctx, ByteBuf payload,
506             Http2FrameListener listener) throws Http2Exception {
507         long errorCode = payload.readUnsignedInt();
508         listener.onRstStreamRead(ctx, streamId, errorCode);
509     }
510 
511     private void readSettingsFrame(ChannelHandlerContext ctx, ByteBuf payload,
512             Http2FrameListener listener) throws Http2Exception {
513         if (flags.ack()) {
514             listener.onSettingsAckRead(ctx);
515         } else {
516             int numSettings = payloadLength / SETTING_ENTRY_LENGTH;
517             Http2Settings settings = new Http2Settings();
518             for (int index = 0; index < numSettings; ++index) {
519                 char id = (char) payload.readUnsignedShort();
520                 long value = payload.readUnsignedInt();
521                 try {
522                     settings.put(id, Long.valueOf(value));
523                 } catch (IllegalArgumentException e) {
524                     if (id == SETTINGS_INITIAL_WINDOW_SIZE) {
525                         throw connectionError(FLOW_CONTROL_ERROR, e,
526                                 "Failed setting initial window size: %s", e.getMessage());
527                     }
528                     throw connectionError(PROTOCOL_ERROR, e, "Protocol error: %s", e.getMessage());
529                 }
530             }
531             listener.onSettingsRead(ctx, settings);
532         }
533     }
534 
535     private void readPushPromiseFrame(final ChannelHandlerContext ctx, ByteBuf payload,
536             Http2FrameListener listener) throws Http2Exception {
537         final int pushPromiseStreamId = streamId;
538         final int padding = readPadding(payload);
539         verifyPadding(padding);
540         final int promisedStreamId = readUnsignedInt(payload);
541 
542         // Create a handler that invokes the listener when the header block is complete.
543         headersContinuation = new HeadersContinuation() {
544             @Override
545             public int getStreamId() {
546                 return pushPromiseStreamId;
547             }
548 
549             @Override
550             public void processFragment(boolean endOfHeaders, ByteBuf fragment, int len,
551                     Http2FrameListener listener) throws Http2Exception {
552                 headersBlockBuilder().addFragment(fragment, len, ctx.alloc(), endOfHeaders);
553                 if (endOfHeaders) {
554                     listener.onPushPromiseRead(ctx, pushPromiseStreamId, promisedStreamId,
555                             headersBlockBuilder().headers(), padding);
556                 }
557             }
558         };
559 
560         // Process the initial fragment, invoking the listener's callback if end of headers.
561         int len = lengthWithoutTrailingPadding(payload.readableBytes(), padding);
562         headersContinuation.processFragment(flags.endOfHeaders(), payload, len, listener);
563         resetHeadersContinuationIfEnd(flags.endOfHeaders());
564     }
565 
566     private void readPingFrame(ChannelHandlerContext ctx, long data,
567             Http2FrameListener listener) throws Http2Exception {
568         if (flags.ack()) {
569             listener.onPingAckRead(ctx, data);
570         } else {
571             listener.onPingRead(ctx, data);
572         }
573     }
574 
575     private void readGoAwayFrame(ChannelHandlerContext ctx, ByteBuf payload,
576             Http2FrameListener listener) throws Http2Exception {
577         int lastStreamId = readUnsignedInt(payload);
578         long errorCode = payload.readUnsignedInt();
579         listener.onGoAwayRead(ctx, lastStreamId, errorCode, payload);
580     }
581 
582     private void readWindowUpdateFrame(ChannelHandlerContext ctx, ByteBuf payload,
583             Http2FrameListener listener) throws Http2Exception {
584         int windowSizeIncrement = readUnsignedInt(payload);
585         if (windowSizeIncrement == 0) {
586             // On the connection stream this must be a connection error but for request streams it is a stream error.
587             // See https://datatracker.ietf.org/doc/html/rfc9113#section-6.9 for details.
588             if (streamId == CONNECTION_STREAM_ID) {
589                 throw connectionError(PROTOCOL_ERROR,
590                         "Received WINDOW_UPDATE with delta 0 for connection stream");
591             } else {
592                 throw streamError(streamId, PROTOCOL_ERROR,
593                         "Received WINDOW_UPDATE with delta 0 for stream: %d", streamId);
594             }
595         }
596         listener.onWindowUpdateRead(ctx, streamId, windowSizeIncrement);
597     }
598 
599     private void readContinuationFrame(ByteBuf payload, Http2FrameListener listener)
600             throws Http2Exception {
601         // Process the initial fragment, invoking the listener's callback if end of headers.
602         headersContinuation.processFragment(flags.endOfHeaders(), payload,
603                 payloadLength, listener);
604         resetHeadersContinuationIfEnd(flags.endOfHeaders());
605     }
606 
607     private void readUnknownFrame(ChannelHandlerContext ctx, ByteBuf payload,
608             Http2FrameListener listener) throws Http2Exception {
609         listener.onUnknownFrame(ctx, frameType, streamId, flags, payload);
610     }
611 
612     /**
613      * If padding is present in the payload, reads the next byte as padding. The padding also includes the one byte
614      * width of the pad length field. Otherwise, returns zero.
615      */
616     private int readPadding(ByteBuf payload) {
617         if (!flags.paddingPresent()) {
618             return 0;
619         }
620         return payload.readUnsignedByte() + 1;
621     }
622 
623     private void verifyPadding(int padding) throws Http2Exception {
624         int len = lengthWithoutTrailingPadding(payloadLength, padding);
625         if (len < 0) {
626             throw connectionError(PROTOCOL_ERROR, "Frame payload too small for padding.");
627         }
628     }
629 
630     /**
631      * The padding parameter consists of the 1 byte pad length field and the trailing padding bytes. This method
632      * returns the number of readable bytes without the trailing padding.
633      */
634     private static int lengthWithoutTrailingPadding(int readableBytes, int padding) {
635         return padding == 0
636                 ? readableBytes
637                 : readableBytes - (padding - 1);
638     }
639 
640     /**
641      * Base class for processing of HEADERS and PUSH_PROMISE header blocks that potentially span
642      * multiple frames. The implementation of this interface will perform the final callback to the
643      * {@link Http2FrameListener} once the end of headers is reached.
644      */
645     private abstract class HeadersContinuation {
646         private final HeadersBlockBuilder builder = new HeadersBlockBuilder();
647 
648         /**
649          * Returns the stream for which headers are currently being processed.
650          */
651         abstract int getStreamId();
652 
653         /**
654          * Processes the next fragment for the current header block.
655          *
656          * @param endOfHeaders whether the fragment is the last in the header block.
657          * @param fragment the fragment of the header block to be added.
658          * @param listener the listener to be notified if the header block is completed.
659          */
660         abstract void processFragment(boolean endOfHeaders, ByteBuf fragment, int len,
661                 Http2FrameListener listener) throws Http2Exception;
662 
663         final HeadersBlockBuilder headersBlockBuilder() {
664             return builder;
665         }
666 
667         /**
668          * Free any allocated resources.
669          */
670         final void close() {
671             builder.close();
672         }
673     }
674 
675     /**
676      * Utility class to help with construction of the headers block that may potentially span
677      * multiple frames.
678      */
679     protected class HeadersBlockBuilder {
680         private ByteBuf headerBlock;
681 
682         /**
683          * The local header size maximum has been exceeded while accumulating bytes.
684          * @throws Http2Exception A connection error indicating too much data has been received.
685          */
686         private void headerSizeExceeded() throws Http2Exception {
687             close();
688             headerListSizeExceeded(headersDecoder.configuration().maxHeaderListSizeGoAway());
689         }
690 
691         /**
692          * Adds a fragment to the block.
693          *
694          * @param fragment the fragment of the headers block to be added.
695          * @param alloc allocator for new blocks if needed.
696          * @param endOfHeaders flag indicating whether the current frame is the end of the headers.
697          *            This is used for an optimization for when the first fragment is the full
698          *            block. In that case, the buffer is used directly without copying.
699          */
700         final void addFragment(ByteBuf fragment, int len, ByteBufAllocator alloc,
701                 boolean endOfHeaders) throws Http2Exception {
702             if (headerBlock == null) {
703                 if (len > headersDecoder.configuration().maxHeaderListSizeGoAway()) {
704                     headerSizeExceeded();
705                 }
706                 if (endOfHeaders) {
707                     // Optimization - don't bother copying, just use the buffer as-is. Need
708                     // to retain since we release when the header block is built.
709                     headerBlock = fragment.readRetainedSlice(len);
710                 } else {
711                     headerBlock = alloc.buffer(len).writeBytes(fragment, len);
712                 }
713                 return;
714             }
715             if (headersDecoder.configuration().maxHeaderListSizeGoAway() - len <
716                     headerBlock.readableBytes()) {
717                 headerSizeExceeded();
718             }
719             if (headerBlock.isWritable(len)) {
720                 // The buffer can hold the requested bytes, just write it directly.
721                 headerBlock.writeBytes(fragment, len);
722             } else {
723                 // Allocate a new buffer that is big enough to hold the entire header block so far.
724                 ByteBuf buf = alloc.buffer(headerBlock.readableBytes() + len);
725                 buf.writeBytes(headerBlock).writeBytes(fragment, len);
726                 headerBlock.release();
727                 headerBlock = buf;
728             }
729         }
730 
731         /**
732          * Builds the headers from the completed headers block. After this is called, this builder
733          * should not be called again.
734          */
735         Http2Headers headers() throws Http2Exception {
736             try {
737                 return headersDecoder.decodeHeaders(streamId, headerBlock);
738             } finally {
739                 close();
740             }
741         }
742 
743         /**
744          * Closes this builder and frees any resources.
745          */
746         void close() {
747             if (headerBlock != null) {
748                 headerBlock.release();
749                 headerBlock = null;
750             }
751 
752             // Clear the member variable pointing at this instance.
753             headersContinuation = null;
754         }
755     }
756 
757     /**
758      * Verify that current state is not processing on header block
759      * @throws Http2Exception thrown if {@link #headersContinuation} is not null
760      */
761     private void verifyNotProcessingHeaders() throws Http2Exception {
762         if (headersContinuation != null) {
763             throw connectionError(PROTOCOL_ERROR, "Received frame of type %s while processing headers on stream %d.",
764                                   frameType, headersContinuation.getStreamId());
765         }
766     }
767 
768     private void verifyAssociatedWithAStream() throws Http2Exception {
769         if (streamId == 0) {
770             throw connectionError(PROTOCOL_ERROR, "Frame of type %s must be associated with a stream.", frameType);
771         }
772     }
773 
774     private static void verifyStreamOrConnectionId(int streamId, String argumentName)
775             throws Http2Exception {
776         if (streamId < 0) {
777             throw connectionError(PROTOCOL_ERROR, "%s must be >= 0", argumentName);
778         }
779     }
780 }