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