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