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