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.netty5.handler.codec.http2;
16  
17  import io.netty5.buffer.api.Buffer;
18  import io.netty5.buffer.api.BufferAllocator;
19  import io.netty5.channel.ChannelHandlerContext;
20  import io.netty5.handler.codec.http2.Http2FrameReader.Configuration;
21  import io.netty5.util.internal.UnstableApi;
22  
23  import static io.netty5.handler.codec.http2.Http2CodecUtil.DEFAULT_MAX_FRAME_SIZE;
24  import static io.netty5.handler.codec.http2.Http2CodecUtil.FRAME_HEADER_LENGTH;
25  import static io.netty5.handler.codec.http2.Http2CodecUtil.INT_FIELD_LENGTH;
26  import static io.netty5.handler.codec.http2.Http2CodecUtil.PING_FRAME_PAYLOAD_LENGTH;
27  import static io.netty5.handler.codec.http2.Http2CodecUtil.PRIORITY_ENTRY_LENGTH;
28  import static io.netty5.handler.codec.http2.Http2CodecUtil.SETTINGS_INITIAL_WINDOW_SIZE;
29  import static io.netty5.handler.codec.http2.Http2CodecUtil.SETTING_ENTRY_LENGTH;
30  import static io.netty5.handler.codec.http2.Http2CodecUtil.headerListSizeExceeded;
31  import static io.netty5.handler.codec.http2.Http2CodecUtil.isMaxFrameSizeValid;
32  import static io.netty5.handler.codec.http2.Http2CodecUtil.readUnsignedInt;
33  import static io.netty5.handler.codec.http2.Http2Error.FLOW_CONTROL_ERROR;
34  import static io.netty5.handler.codec.http2.Http2Error.FRAME_SIZE_ERROR;
35  import static io.netty5.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
36  import static io.netty5.handler.codec.http2.Http2Exception.connectionError;
37  import static io.netty5.handler.codec.http2.Http2Exception.streamError;
38  import static io.netty5.handler.codec.http2.Http2FrameTypes.CONTINUATION;
39  import static io.netty5.handler.codec.http2.Http2FrameTypes.DATA;
40  import static io.netty5.handler.codec.http2.Http2FrameTypes.GO_AWAY;
41  import static io.netty5.handler.codec.http2.Http2FrameTypes.HEADERS;
42  import static io.netty5.handler.codec.http2.Http2FrameTypes.PING;
43  import static io.netty5.handler.codec.http2.Http2FrameTypes.PRIORITY;
44  import static io.netty5.handler.codec.http2.Http2FrameTypes.PUSH_PROMISE;
45  import static io.netty5.handler.codec.http2.Http2FrameTypes.RST_STREAM;
46  import static io.netty5.handler.codec.http2.Http2FrameTypes.SETTINGS;
47  import static io.netty5.handler.codec.http2.Http2FrameTypes.WINDOW_UPDATE;
48  
49  /**
50   * A {@link Http2FrameReader} that supports all frame types defined by the HTTP/2 specification.
51   */
52  @UnstableApi
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             throw streamError(streamId, FRAME_SIZE_ERROR,
114                     "Invalid MAX_FRAME_SIZE specified in sent settings: %d", max);
115         }
116         maxFrameSize = max;
117     }
118 
119     @Override
120     public int maxFrameSize() {
121         return maxFrameSize;
122     }
123 
124     @Override
125     public void close() {
126         closeHeadersContinuation();
127     }
128 
129     private void closeHeadersContinuation() {
130         if (headersContinuation != null) {
131             headersContinuation.close();
132             headersContinuation = null;
133         }
134     }
135 
136     @Override
137     public void readFrame(ChannelHandlerContext ctx, Buffer input, Http2FrameListener listener)
138             throws Http2Exception {
139         if (readError) {
140             input.skipReadableBytes(input.readableBytes());
141             return;
142         }
143         try {
144             do {
145                 if (readingHeaders) {
146                     processHeaderState(input);
147                     if (readingHeaders) {
148                         // Wait until the entire header has arrived.
149                         return;
150                     }
151                 }
152 
153                 // The header is complete, fall into the next case to process the payload.
154                 // This is to ensure the proper handling of zero-length payloads. In this
155                 // case, we don't want to loop around because there may be no more data
156                 // available, causing us to exit the loop. Instead, we just want to perform
157                 // the first pass at payload processing now.
158                 processPayloadState(ctx, input, listener);
159                 if (!readingHeaders) {
160                     // Wait until the entire payload has arrived.
161                     return;
162                 }
163             } while (input.readableBytes() > 0);
164         } catch (Http2Exception e) {
165             readError = !Http2Exception.isStreamError(e);
166             throw e;
167         } catch (Throwable e) {
168             readError = true;
169             throw e;
170         }
171     }
172 
173     private void processHeaderState(Buffer in) throws Http2Exception {
174         if (in.readableBytes() < FRAME_HEADER_LENGTH) {
175             // Wait until the entire frame header has been read.
176             return;
177         }
178 
179         // Read the header and prepare the unmarshaller to read the frame.
180         payloadLength = in.readUnsignedMedium();
181         if (payloadLength > maxFrameSize) {
182             throw connectionError(FRAME_SIZE_ERROR, "Frame length: %d exceeds maximum: %d", payloadLength,
183                                   maxFrameSize);
184         }
185         frameType = in.readByte();
186         flags = new Http2Flags((short) in.readUnsignedByte());
187         streamId = readUnsignedInt(in);
188 
189         // We have consumed the data, next time we read we will be expecting to read the frame payload.
190         readingHeaders = false;
191 
192         switch (frameType) {
193             case DATA:
194                 verifyDataFrame();
195                 break;
196             case HEADERS:
197                 verifyHeadersFrame();
198                 break;
199             case PRIORITY:
200                 verifyPriorityFrame();
201                 break;
202             case RST_STREAM:
203                 verifyRstStreamFrame();
204                 break;
205             case SETTINGS:
206                 verifySettingsFrame();
207                 break;
208             case PUSH_PROMISE:
209                 verifyPushPromiseFrame();
210                 break;
211             case PING:
212                 verifyPingFrame();
213                 break;
214             case GO_AWAY:
215                 verifyGoAwayFrame();
216                 break;
217             case WINDOW_UPDATE:
218                 verifyWindowUpdateFrame();
219                 break;
220             case CONTINUATION:
221                 verifyContinuationFrame();
222                 break;
223             default:
224                 // Unknown frame type, could be an extension.
225                 verifyUnknownFrame();
226                 break;
227         }
228     }
229 
230     private void processPayloadState(ChannelHandlerContext ctx, Buffer in, Http2FrameListener listener)
231                     throws Http2Exception {
232         if (in.readableBytes() < payloadLength) {
233             // Wait until the entire payload has been read.
234             return;
235         }
236 
237         // Only process up to payloadLength bytes.
238         Buffer payload = in.readSplit(payloadLength);
239 
240         // We have consumed the data, next time we read we will be expecting to read a frame header.
241         readingHeaders = true;
242 
243         // Read the payload and fire the frame event to the listener.
244         switch (frameType) {
245         case DATA:
246             readDataFrame(ctx, payload, listener);
247             break;
248         case HEADERS:
249             readHeadersFrame(ctx, payload, listener);
250             break;
251         case PRIORITY:
252             readPriorityFrame(ctx, payload, listener);
253             break;
254         case RST_STREAM:
255             readRstStreamFrame(ctx, payload, listener);
256             break;
257         case SETTINGS:
258             readSettingsFrame(ctx, payload, listener);
259             break;
260         case PUSH_PROMISE:
261             readPushPromiseFrame(ctx, payload, listener);
262             break;
263         case PING:
264             readPingFrame(ctx, payload, listener);
265             break;
266         case GO_AWAY:
267             readGoAwayFrame(ctx, payload, listener);
268             break;
269         case WINDOW_UPDATE:
270             readWindowUpdateFrame(ctx, payload, listener);
271             break;
272         case CONTINUATION:
273             readContinuationFrame(payload, listener);
274             break;
275         default:
276             readUnknownFrame(ctx, payload, 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             throw streamError(streamId, FRAME_SIZE_ERROR,
298                     "Frame length too small." + payloadLength);
299         }
300     }
301 
302     private void verifyPriorityFrame() throws Http2Exception {
303         verifyAssociatedWithAStream();
304         verifyNotProcessingHeaders();
305 
306         if (payloadLength != PRIORITY_ENTRY_LENGTH) {
307             throw streamError(streamId, FRAME_SIZE_ERROR,
308                     "Invalid frame length %d.", payloadLength);
309         }
310     }
311 
312     private void verifyRstStreamFrame() throws Http2Exception {
313         verifyAssociatedWithAStream();
314         verifyNotProcessingHeaders();
315 
316         if (payloadLength != INT_FIELD_LENGTH) {
317             throw connectionError(FRAME_SIZE_ERROR, "Invalid frame length %d.", payloadLength);
318         }
319     }
320 
321     private void verifySettingsFrame() throws Http2Exception {
322         verifyNotProcessingHeaders();
323         if (streamId != 0) {
324             throw connectionError(PROTOCOL_ERROR, "A stream ID must be zero.");
325         }
326         if (flags.ack() && payloadLength > 0) {
327             throw connectionError(FRAME_SIZE_ERROR, "Ack settings frame must have an empty payload.");
328         }
329         if (payloadLength % SETTING_ENTRY_LENGTH > 0) {
330             throw connectionError(FRAME_SIZE_ERROR, "Frame length %d invalid.", payloadLength);
331         }
332     }
333 
334     private void verifyPushPromiseFrame() throws Http2Exception {
335         verifyNotProcessingHeaders();
336 
337         // Subtract the length of the promised stream ID field, to determine the length of the
338         // rest of the payload (header block fragment + payload).
339         int minLength = flags.getPaddingPresenceFieldLength() + INT_FIELD_LENGTH;
340         if (payloadLength < minLength) {
341             throw streamError(streamId, FRAME_SIZE_ERROR,
342                     "Frame length %d too small.", payloadLength);
343         }
344     }
345 
346     private void verifyPingFrame() throws Http2Exception {
347         verifyNotProcessingHeaders();
348         if (streamId != 0) {
349             throw connectionError(PROTOCOL_ERROR, "A stream ID must be zero.");
350         }
351         if (payloadLength != PING_FRAME_PAYLOAD_LENGTH) {
352             throw connectionError(FRAME_SIZE_ERROR,
353                     "Frame length %d incorrect size for ping.", payloadLength);
354         }
355     }
356 
357     private void verifyGoAwayFrame() throws Http2Exception {
358         verifyNotProcessingHeaders();
359 
360         if (streamId != 0) {
361             throw connectionError(PROTOCOL_ERROR, "A stream ID must be zero.");
362         }
363         if (payloadLength < 8) {
364             throw connectionError(FRAME_SIZE_ERROR, "Frame length %d too small.", payloadLength);
365         }
366     }
367 
368     private void verifyWindowUpdateFrame() throws Http2Exception {
369         verifyNotProcessingHeaders();
370         verifyStreamOrConnectionId(streamId, "Stream ID");
371 
372         if (payloadLength != INT_FIELD_LENGTH) {
373             throw connectionError(FRAME_SIZE_ERROR, "Invalid frame length %d.", payloadLength);
374         }
375     }
376 
377     private void verifyContinuationFrame() throws Http2Exception {
378         verifyAssociatedWithAStream();
379 
380         if (headersContinuation == null) {
381             throw connectionError(PROTOCOL_ERROR, "Received %s frame but not currently processing headers.",
382                     frameType);
383         }
384 
385         if (streamId != headersContinuation.getStreamId()) {
386             throw connectionError(PROTOCOL_ERROR, "Continuation stream ID does not match pending headers. "
387                     + "Expected %d, but received %d.", headersContinuation.getStreamId(), streamId);
388         }
389 
390         if (payloadLength < flags.getPaddingPresenceFieldLength()) {
391             throw streamError(streamId, FRAME_SIZE_ERROR,
392                     "Frame length %d too small for padding.", payloadLength);
393         }
394     }
395 
396     private void verifyUnknownFrame() throws Http2Exception {
397         verifyNotProcessingHeaders();
398     }
399 
400     private void readDataFrame(ChannelHandlerContext ctx, Buffer payload, Http2FrameListener listener)
401             throws Http2Exception {
402         try (payload) { // Close payload because we split off the data.
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             Buffer data = payload.readSplit(dataLength);
411             listener.onDataRead(ctx, streamId, data, padding, flags.endOfStream());
412         }
413     }
414 
415     private void readHeadersFrame(final ChannelHandlerContext ctx, Buffer payload,
416             Http2FrameListener listener) throws Http2Exception {
417         final int headersStreamId = streamId;
418         final Http2Flags headersFlags = flags;
419         final int padding = readPadding(payload);
420         verifyPadding(padding);
421 
422         // The callback that is invoked is different depending on whether priority information
423         // is present in the headers frame.
424         if (flags.priorityPresent()) {
425             long word1 = payload.readUnsignedInt();
426             final boolean exclusive = (word1 & 0x80000000L) != 0;
427             final int streamDependency = (int) (word1 & 0x7FFFFFFFL);
428             if (streamDependency == streamId) {
429                 throw streamError(streamId, PROTOCOL_ERROR, "A stream cannot depend on itself.");
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, Buffer fragment, int len,
443                         Http2FrameListener listener) throws Http2Exception {
444                     final HeadersBlockBuilder hdrBlockBuilder = headersBlockBuilder();
445                     hdrBlockBuilder.addFragment(fragment, len, ctx.bufferAllocator(), 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, Buffer fragment, int len,
469                     Http2FrameListener listener) throws Http2Exception {
470                 final HeadersBlockBuilder hdrBlockBuilder = headersBlockBuilder();
471                 hdrBlockBuilder.addFragment(fragment, len, ctx.bufferAllocator(), 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, Buffer 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, Buffer 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, Buffer 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, Buffer payload,
534             Http2FrameListener listener) throws Http2Exception {
535         final int pushPromiseStreamId = streamId;
536         final int padding = readPadding(payload);
537         verifyPadding(padding);
538         final int promisedStreamId = readUnsignedInt(payload);
539 
540         // Create a handler that invokes the listener when the header block is complete.
541         headersContinuation = new HeadersContinuation() {
542             @Override
543             public int getStreamId() {
544                 return pushPromiseStreamId;
545             }
546 
547             @Override
548             public void processFragment(boolean endOfHeaders, Buffer fragment, int len,
549                     Http2FrameListener listener) throws Http2Exception {
550                 headersBlockBuilder().addFragment(fragment, len, ctx.bufferAllocator(), endOfHeaders);
551                 if (endOfHeaders) {
552                     listener.onPushPromiseRead(ctx, pushPromiseStreamId, promisedStreamId,
553                             headersBlockBuilder().headers(), padding);
554                 }
555             }
556         };
557 
558         // Process the initial fragment, invoking the listener's callback if end of headers.
559         int len = lengthWithoutTrailingPadding(payload.readableBytes(), padding);
560         headersContinuation.processFragment(flags.endOfHeaders(), payload, len, listener);
561         resetHeadersContinuationIfEnd(flags.endOfHeaders());
562     }
563 
564     private void readPingFrame(ChannelHandlerContext ctx, Buffer payload,
565             Http2FrameListener listener) throws Http2Exception {
566         try (payload) {
567             long data = payload.readLong();
568             if (flags.ack()) {
569                 listener.onPingAckRead(ctx, data);
570             } else {
571                 listener.onPingRead(ctx, data);
572             }
573         }
574     }
575 
576     private static void readGoAwayFrame(ChannelHandlerContext ctx, Buffer payload,
577                                         Http2FrameListener listener) throws Http2Exception {
578         int lastStreamId = readUnsignedInt(payload);
579         long errorCode = payload.readUnsignedInt();
580         Buffer debugData = payload.readSplit(payload.readableBytes());
581         listener.onGoAwayRead(ctx, lastStreamId, errorCode, debugData);
582     }
583 
584     private void readWindowUpdateFrame(ChannelHandlerContext ctx, Buffer payload,
585             Http2FrameListener listener) throws Http2Exception {
586         int windowSizeIncrement = readUnsignedInt(payload);
587         if (windowSizeIncrement == 0) {
588             throw streamError(streamId, PROTOCOL_ERROR,
589                     "Received WINDOW_UPDATE with delta 0 for stream: %d", streamId);
590         }
591         listener.onWindowUpdateRead(ctx, streamId, windowSizeIncrement);
592     }
593 
594     private void readContinuationFrame(Buffer payload, Http2FrameListener listener)
595             throws Http2Exception {
596         // Process the initial fragment, invoking the listener's callback if end of headers.
597         headersContinuation.processFragment(flags.endOfHeaders(), payload,
598                 payload.readableBytes(), listener);
599         resetHeadersContinuationIfEnd(flags.endOfHeaders());
600     }
601 
602     private void readUnknownFrame(ChannelHandlerContext ctx, Buffer payload, Http2FrameListener listener)
603             throws Http2Exception {
604         listener.onUnknownFrame(ctx, frameType, streamId, flags, payload);
605     }
606 
607     /**
608      * If padding is present in the payload, reads the next byte as padding. The padding also includes the one byte
609      * width of the pad length field. Otherwise, returns zero.
610      */
611     private int readPadding(Buffer payload) {
612         if (!flags.paddingPresent()) {
613             return 0;
614         }
615         return payload.readUnsignedByte() + 1;
616     }
617 
618     private void verifyPadding(int padding) throws Http2Exception {
619         int len = lengthWithoutTrailingPadding(payloadLength, padding);
620         if (len < 0) {
621             throw connectionError(PROTOCOL_ERROR, "Frame payload too small for padding.");
622         }
623     }
624 
625     /**
626      * The padding parameter consists of the 1 byte pad length field and the trailing padding bytes. This method
627      * returns the number of readable bytes without the trailing padding.
628      */
629     private static int lengthWithoutTrailingPadding(int readableBytes, int padding) {
630         return padding == 0
631                 ? readableBytes
632                 : readableBytes - (padding - 1);
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, Buffer 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 Buffer 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(Buffer fragment, int len, BufferAllocator 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.readSplit(len);
705                 } else {
706                     headerBlock = alloc.allocate(len);
707                     fragment.copyInto(fragment.readerOffset(), headerBlock, 0, len);
708                     headerBlock.skipWritableBytes(len);
709                     fragment.skipReadableBytes(len);
710                 }
711                 return;
712             }
713             if (headersDecoder.configuration().maxHeaderListSizeGoAway() - len <
714                     headerBlock.readableBytes()) {
715                 headerSizeExceeded();
716             }
717             if (headerBlock.writableBytes() >= len) {
718                 // The buffer can hold the requested bytes, just write it directly.
719                 fragment.copyInto(fragment.readerOffset(), headerBlock, headerBlock.readerOffset(), len);
720                 headerBlock.skipWritableBytes(len);
721             } else {
722                 // Allocate a new buffer that is big enough to hold the entire header block so far.
723                 Buffer buf = alloc.allocate(headerBlock.readableBytes() + len);
724                 buf.writeBytes(headerBlock);
725                 fragment.copyInto(fragment.readerOffset(), buf, buf.writerOffset(), len);
726                 buf.skipWritableBytes(len);
727                 fragment.skipReadableBytes(len);
728                 headerBlock.close();
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.close();
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 }