1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package io.netty.handler.codec.http2;
16
17 import io.netty.buffer.ByteBuf;
18 import io.netty.buffer.ByteBufAllocator;
19 import io.netty.channel.ChannelHandlerContext;
20 import io.netty.handler.codec.http2.Http2FrameReader.Configuration;
21 import io.netty.util.internal.PlatformDependent;
22
23 import static io.netty.handler.codec.http2.Http2CodecUtil.CONNECTION_STREAM_ID;
24 import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_MAX_FRAME_SIZE;
25 import static io.netty.handler.codec.http2.Http2CodecUtil.FRAME_HEADER_LENGTH;
26 import static io.netty.handler.codec.http2.Http2CodecUtil.INT_FIELD_LENGTH;
27 import static io.netty.handler.codec.http2.Http2CodecUtil.PING_FRAME_PAYLOAD_LENGTH;
28 import static io.netty.handler.codec.http2.Http2CodecUtil.PRIORITY_ENTRY_LENGTH;
29 import static io.netty.handler.codec.http2.Http2CodecUtil.SETTINGS_INITIAL_WINDOW_SIZE;
30 import static io.netty.handler.codec.http2.Http2CodecUtil.SETTING_ENTRY_LENGTH;
31 import static io.netty.handler.codec.http2.Http2CodecUtil.headerListSizeExceeded;
32 import static io.netty.handler.codec.http2.Http2CodecUtil.isMaxFrameSizeValid;
33 import static io.netty.handler.codec.http2.Http2CodecUtil.readUnsignedInt;
34 import static io.netty.handler.codec.http2.Http2Error.FLOW_CONTROL_ERROR;
35 import static io.netty.handler.codec.http2.Http2Error.FRAME_SIZE_ERROR;
36 import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
37 import static io.netty.handler.codec.http2.Http2Exception.connectionError;
38 import static io.netty.handler.codec.http2.Http2Exception.streamError;
39 import static io.netty.handler.codec.http2.Http2FrameTypes.CONTINUATION;
40 import static io.netty.handler.codec.http2.Http2FrameTypes.DATA;
41 import static io.netty.handler.codec.http2.Http2FrameTypes.GO_AWAY;
42 import static io.netty.handler.codec.http2.Http2FrameTypes.HEADERS;
43 import static io.netty.handler.codec.http2.Http2FrameTypes.PING;
44 import static io.netty.handler.codec.http2.Http2FrameTypes.PRIORITY;
45 import static io.netty.handler.codec.http2.Http2FrameTypes.PUSH_PROMISE;
46 import static io.netty.handler.codec.http2.Http2FrameTypes.RST_STREAM;
47 import static io.netty.handler.codec.http2.Http2FrameTypes.SETTINGS;
48 import static io.netty.handler.codec.http2.Http2FrameTypes.WINDOW_UPDATE;
49
50
51
52
53 public class DefaultHttp2FrameReader implements Http2FrameReader, Http2FrameSizePolicy, Configuration {
54 private final Http2HeadersDecoder headersDecoder;
55
56
57
58
59 private boolean readingHeaders = true;
60
61
62
63
64 private boolean readError;
65 private byte frameType;
66 private int streamId;
67 private Http2Flags flags;
68 private int payloadLength;
69 private HeadersContinuation headersContinuation;
70 private int maxFrameSize;
71
72
73
74
75
76
77 public DefaultHttp2FrameReader() {
78 this(true);
79 }
80
81
82
83
84
85
86 public DefaultHttp2FrameReader(boolean validateHeaders) {
87 this(new DefaultHttp2HeadersDecoder(validateHeaders));
88 }
89
90 public DefaultHttp2FrameReader(Http2HeadersDecoder headersDecoder) {
91 this.headersDecoder = headersDecoder;
92 maxFrameSize = DEFAULT_MAX_FRAME_SIZE;
93 }
94
95 @Override
96 public Http2HeadersDecoder.Configuration headersConfiguration() {
97 return headersDecoder.configuration();
98 }
99
100 @Override
101 public Configuration configuration() {
102 return this;
103 }
104
105 @Override
106 public Http2FrameSizePolicy frameSizePolicy() {
107 return this;
108 }
109
110 @Override
111 public void maxFrameSize(int max) throws Http2Exception {
112 if (!isMaxFrameSizeValid(max)) {
113
114
115 throw connectionError(FRAME_SIZE_ERROR, "Invalid MAX_FRAME_SIZE specified in sent settings: %d", max);
116 }
117 maxFrameSize = max;
118 }
119
120 @Override
121 public int maxFrameSize() {
122 return maxFrameSize;
123 }
124
125 @Override
126 public void close() {
127 closeHeadersContinuation();
128 }
129
130 private void closeHeadersContinuation() {
131 if (headersContinuation != null) {
132 headersContinuation.close();
133 headersContinuation = null;
134 }
135 }
136
137 @Override
138 public void readFrame(ChannelHandlerContext ctx, ByteBuf input, Http2FrameListener listener)
139 throws Http2Exception {
140 if (readError) {
141 input.skipBytes(input.readableBytes());
142 return;
143 }
144 try {
145 do {
146 if (readingHeaders && !preProcessFrame(input)) {
147 return;
148 }
149
150
151
152
153
154
155 if (input.readableBytes() < payloadLength) {
156 return;
157 }
158
159 ByteBuf framePayload = input.readSlice(payloadLength);
160
161
162 readingHeaders = true;
163 verifyFrameState();
164 processPayloadState(ctx, framePayload, listener);
165 } while (input.isReadable());
166 } catch (Http2Exception e) {
167 readError = !Http2Exception.isStreamError(e);
168 throw e;
169 } catch (RuntimeException e) {
170 readError = true;
171 throw e;
172 } catch (Throwable cause) {
173 readError = true;
174 PlatformDependent.throwException(cause);
175 }
176 }
177
178 private boolean preProcessFrame(ByteBuf in) throws Http2Exception {
179
180
181 if (in.readableBytes() < FRAME_HEADER_LENGTH) {
182
183 return false;
184 }
185 payloadLength = in.readUnsignedMedium();
186 if (payloadLength > maxFrameSize) {
187 throw connectionError(FRAME_SIZE_ERROR, "Frame length: %d exceeds maximum: %d", payloadLength,
188 maxFrameSize);
189 }
190 frameType = in.readByte();
191 flags = new Http2Flags(in.readUnsignedByte());
192 streamId = readUnsignedInt(in);
193 readingHeaders = false;
194 return true;
195 }
196
197 private void verifyFrameState() throws Http2Exception {
198 switch (frameType) {
199 case DATA:
200 verifyDataFrame();
201 break;
202 case HEADERS:
203 verifyHeadersFrame();
204 break;
205 case PRIORITY:
206 verifyPriorityFrame();
207 break;
208 case RST_STREAM:
209 verifyRstStreamFrame();
210 break;
211 case SETTINGS:
212 verifySettingsFrame();
213 break;
214 case PUSH_PROMISE:
215 verifyPushPromiseFrame();
216 break;
217 case PING:
218 verifyPingFrame();
219 break;
220 case GO_AWAY:
221 verifyGoAwayFrame();
222 break;
223 case WINDOW_UPDATE:
224 verifyWindowUpdateFrame();
225 break;
226 case CONTINUATION:
227 verifyContinuationFrame();
228 break;
229 default:
230
231 verifyUnknownFrame();
232 break;
233 }
234 }
235
236 private void processPayloadState(ChannelHandlerContext ctx, ByteBuf in, Http2FrameListener listener)
237 throws Http2Exception {
238
239
240 assert in.readableBytes() == payloadLength;
241
242 switch (frameType) {
243 case DATA:
244 readDataFrame(ctx, in, listener);
245 break;
246 case HEADERS:
247 readHeadersFrame(ctx, in, listener);
248 break;
249 case PRIORITY:
250 readPriorityFrame(ctx, in, listener);
251 break;
252 case RST_STREAM:
253 readRstStreamFrame(ctx, in, listener);
254 break;
255 case SETTINGS:
256 readSettingsFrame(ctx, in, listener);
257 break;
258 case PUSH_PROMISE:
259 readPushPromiseFrame(ctx, in, listener);
260 break;
261 case PING:
262 readPingFrame(ctx, in.readLong(), listener);
263 break;
264 case GO_AWAY:
265 readGoAwayFrame(ctx, in, listener);
266 break;
267 case WINDOW_UPDATE:
268 readWindowUpdateFrame(ctx, in, listener);
269 break;
270 case CONTINUATION:
271 readContinuationFrame(in, listener);
272 break;
273 default:
274 readUnknownFrame(ctx, in, listener);
275 break;
276 }
277 }
278
279 private void verifyDataFrame() throws Http2Exception {
280 verifyAssociatedWithAStream();
281 verifyNotProcessingHeaders();
282
283 if (payloadLength < flags.getPaddingPresenceFieldLength()) {
284 throw streamError(streamId, FRAME_SIZE_ERROR,
285 "Frame length %d too small.", payloadLength);
286 }
287 }
288
289 private void verifyHeadersFrame() throws Http2Exception {
290 verifyAssociatedWithAStream();
291 verifyNotProcessingHeaders();
292
293 int requiredLength = flags.getPaddingPresenceFieldLength() + flags.getNumPriorityBytes();
294 if (payloadLength < requiredLength) {
295
296
297
298 throw connectionError(FRAME_SIZE_ERROR,
299 "Frame length %d too small for HEADERS frame with stream %d.", payloadLength, streamId);
300 }
301 }
302
303 private void verifyPriorityFrame() throws Http2Exception {
304 verifyAssociatedWithAStream();
305 verifyNotProcessingHeaders();
306
307 if (payloadLength != PRIORITY_ENTRY_LENGTH) {
308 throw streamError(streamId, FRAME_SIZE_ERROR,
309 "Invalid frame length %d.", payloadLength);
310 }
311 }
312
313 private void verifyRstStreamFrame() throws Http2Exception {
314 verifyAssociatedWithAStream();
315 verifyNotProcessingHeaders();
316
317 if (payloadLength != INT_FIELD_LENGTH) {
318 throw connectionError(FRAME_SIZE_ERROR, "Invalid frame length %d.", payloadLength);
319 }
320 }
321
322 private void verifySettingsFrame() throws Http2Exception {
323 verifyNotProcessingHeaders();
324 if (streamId != 0) {
325 throw connectionError(PROTOCOL_ERROR, "A stream ID must be zero.");
326 }
327 if (flags.ack() && payloadLength > 0) {
328 throw connectionError(FRAME_SIZE_ERROR, "Ack settings frame must have an empty payload.");
329 }
330 if (payloadLength % SETTING_ENTRY_LENGTH > 0) {
331 throw connectionError(FRAME_SIZE_ERROR, "Frame length %d invalid.", payloadLength);
332 }
333 }
334
335 private void verifyPushPromiseFrame() throws Http2Exception {
336 verifyNotProcessingHeaders();
337
338
339
340 int minLength = flags.getPaddingPresenceFieldLength() + INT_FIELD_LENGTH;
341 if (payloadLength < minLength) {
342
343
344
345 throw connectionError(FRAME_SIZE_ERROR,
346 "Frame length %d too small for PUSH_PROMISE frame with stream id %d.", payloadLength, streamId);
347 }
348 }
349
350 private void verifyPingFrame() throws Http2Exception {
351 verifyNotProcessingHeaders();
352 if (streamId != 0) {
353 throw connectionError(PROTOCOL_ERROR, "A stream ID must be zero.");
354 }
355 if (payloadLength != PING_FRAME_PAYLOAD_LENGTH) {
356 throw connectionError(FRAME_SIZE_ERROR,
357 "Frame length %d incorrect size for ping.", payloadLength);
358 }
359 }
360
361 private void verifyGoAwayFrame() throws Http2Exception {
362 verifyNotProcessingHeaders();
363
364 if (streamId != 0) {
365 throw connectionError(PROTOCOL_ERROR, "A stream ID must be zero.");
366 }
367 if (payloadLength < 8) {
368 throw connectionError(FRAME_SIZE_ERROR, "Frame length %d too small.", payloadLength);
369 }
370 }
371
372 private void verifyWindowUpdateFrame() throws Http2Exception {
373 verifyNotProcessingHeaders();
374 verifyStreamOrConnectionId(streamId, "Stream ID");
375
376 if (payloadLength != INT_FIELD_LENGTH) {
377 throw connectionError(FRAME_SIZE_ERROR, "Invalid frame length %d.", payloadLength);
378 }
379 }
380
381 private void verifyContinuationFrame() throws Http2Exception {
382 verifyAssociatedWithAStream();
383
384 if (headersContinuation == null) {
385 throw connectionError(PROTOCOL_ERROR, "Received %s frame but not currently processing headers.",
386 frameType);
387 }
388
389 if (streamId != headersContinuation.getStreamId()) {
390 throw connectionError(PROTOCOL_ERROR, "Continuation stream ID does not match pending headers. "
391 + "Expected %d, but received %d.", headersContinuation.getStreamId(), streamId);
392 }
393 }
394
395 private void verifyUnknownFrame() throws Http2Exception {
396 verifyNotProcessingHeaders();
397 }
398
399 private void readDataFrame(ChannelHandlerContext ctx, ByteBuf payload,
400 Http2FrameListener listener) throws Http2Exception {
401 int padding = readPadding(payload);
402 verifyPadding(padding);
403
404
405
406 int dataLength = lengthWithoutTrailingPadding(payload.readableBytes(), padding);
407
408 payload.writerIndex(payload.readerIndex() + dataLength);
409 listener.onDataRead(ctx, streamId, payload, padding, flags.endOfStream());
410 }
411
412 private void readHeadersFrame(final ChannelHandlerContext ctx, ByteBuf payload,
413 Http2FrameListener listener) throws Http2Exception {
414 final int headersStreamId = streamId;
415 final Http2Flags headersFlags = flags;
416 final int padding = readPadding(payload);
417 verifyPadding(padding);
418
419
420
421 if (flags.priorityPresent()) {
422 long word1 = payload.readUnsignedInt();
423 final boolean exclusive = (word1 & 0x80000000L) != 0;
424 final int streamDependency = (int) (word1 & 0x7FFFFFFFL);
425 if (streamDependency == streamId) {
426
427
428
429
430 throw connectionError(
431 PROTOCOL_ERROR, "HEADERS frame for stream %d cannot depend on itself.", streamId);
432 }
433 final short weight = (short) (payload.readUnsignedByte() + 1);
434 final int lenToRead = lengthWithoutTrailingPadding(payload.readableBytes(), padding);
435
436
437 headersContinuation = new HeadersContinuation() {
438 @Override
439 public int getStreamId() {
440 return headersStreamId;
441 }
442
443 @Override
444 public void processFragment(boolean endOfHeaders, ByteBuf fragment, int len,
445 Http2FrameListener listener) throws Http2Exception {
446 final HeadersBlockBuilder hdrBlockBuilder = headersBlockBuilder();
447 hdrBlockBuilder.addFragment(fragment, len, ctx.alloc(), endOfHeaders);
448 if (endOfHeaders) {
449 listener.onHeadersRead(ctx, headersStreamId, hdrBlockBuilder.headers(), streamDependency,
450 weight, exclusive, padding, headersFlags.endOfStream());
451 }
452 }
453 };
454
455
456 headersContinuation.processFragment(flags.endOfHeaders(), payload, lenToRead, listener);
457 resetHeadersContinuationIfEnd(flags.endOfHeaders());
458 return;
459 }
460
461
462
463 headersContinuation = new HeadersContinuation() {
464 @Override
465 public int getStreamId() {
466 return headersStreamId;
467 }
468
469 @Override
470 public void processFragment(boolean endOfHeaders, ByteBuf fragment, int len,
471 Http2FrameListener listener) throws Http2Exception {
472 final HeadersBlockBuilder hdrBlockBuilder = headersBlockBuilder();
473 hdrBlockBuilder.addFragment(fragment, len, ctx.alloc(), endOfHeaders);
474 if (endOfHeaders) {
475 listener.onHeadersRead(ctx, headersStreamId, hdrBlockBuilder.headers(), padding,
476 headersFlags.endOfStream());
477 }
478 }
479 };
480
481
482 int len = lengthWithoutTrailingPadding(payload.readableBytes(), padding);
483 headersContinuation.processFragment(flags.endOfHeaders(), payload, len, listener);
484 resetHeadersContinuationIfEnd(flags.endOfHeaders());
485 }
486
487 private void resetHeadersContinuationIfEnd(boolean endOfHeaders) {
488 if (endOfHeaders) {
489 closeHeadersContinuation();
490 }
491 }
492
493 private void readPriorityFrame(ChannelHandlerContext ctx, ByteBuf payload,
494 Http2FrameListener listener) throws Http2Exception {
495 long word1 = payload.readUnsignedInt();
496 boolean exclusive = (word1 & 0x80000000L) != 0;
497 int streamDependency = (int) (word1 & 0x7FFFFFFFL);
498 if (streamDependency == streamId) {
499 throw streamError(streamId, PROTOCOL_ERROR, "A stream cannot depend on itself.");
500 }
501 short weight = (short) (payload.readUnsignedByte() + 1);
502 listener.onPriorityRead(ctx, streamId, streamDependency, weight, exclusive);
503 }
504
505 private void readRstStreamFrame(ChannelHandlerContext ctx, ByteBuf payload,
506 Http2FrameListener listener) throws Http2Exception {
507 long errorCode = payload.readUnsignedInt();
508 listener.onRstStreamRead(ctx, streamId, errorCode);
509 }
510
511 private void readSettingsFrame(ChannelHandlerContext ctx, ByteBuf payload,
512 Http2FrameListener listener) throws Http2Exception {
513 if (flags.ack()) {
514 listener.onSettingsAckRead(ctx);
515 } else {
516 int numSettings = payloadLength / SETTING_ENTRY_LENGTH;
517 Http2Settings settings = new Http2Settings();
518 for (int index = 0; index < numSettings; ++index) {
519 char id = (char) payload.readUnsignedShort();
520 long value = payload.readUnsignedInt();
521 try {
522 settings.put(id, Long.valueOf(value));
523 } catch (IllegalArgumentException e) {
524 if (id == SETTINGS_INITIAL_WINDOW_SIZE) {
525 throw connectionError(FLOW_CONTROL_ERROR, e,
526 "Failed setting initial window size: %s", e.getMessage());
527 }
528 throw connectionError(PROTOCOL_ERROR, e, "Protocol error: %s", e.getMessage());
529 }
530 }
531 listener.onSettingsRead(ctx, settings);
532 }
533 }
534
535 private void readPushPromiseFrame(final ChannelHandlerContext ctx, ByteBuf payload,
536 Http2FrameListener listener) throws Http2Exception {
537 final int pushPromiseStreamId = streamId;
538 final int padding = readPadding(payload);
539 verifyPadding(padding);
540 final int promisedStreamId = readUnsignedInt(payload);
541
542
543 headersContinuation = new HeadersContinuation() {
544 @Override
545 public int getStreamId() {
546 return pushPromiseStreamId;
547 }
548
549 @Override
550 public void processFragment(boolean endOfHeaders, ByteBuf fragment, int len,
551 Http2FrameListener listener) throws Http2Exception {
552 headersBlockBuilder().addFragment(fragment, len, ctx.alloc(), endOfHeaders);
553 if (endOfHeaders) {
554 listener.onPushPromiseRead(ctx, pushPromiseStreamId, promisedStreamId,
555 headersBlockBuilder().headers(), padding);
556 }
557 }
558 };
559
560
561 int len = lengthWithoutTrailingPadding(payload.readableBytes(), padding);
562 headersContinuation.processFragment(flags.endOfHeaders(), payload, len, listener);
563 resetHeadersContinuationIfEnd(flags.endOfHeaders());
564 }
565
566 private void readPingFrame(ChannelHandlerContext ctx, long data,
567 Http2FrameListener listener) throws Http2Exception {
568 if (flags.ack()) {
569 listener.onPingAckRead(ctx, data);
570 } else {
571 listener.onPingRead(ctx, data);
572 }
573 }
574
575 private void readGoAwayFrame(ChannelHandlerContext ctx, ByteBuf payload,
576 Http2FrameListener listener) throws Http2Exception {
577 int lastStreamId = readUnsignedInt(payload);
578 long errorCode = payload.readUnsignedInt();
579 listener.onGoAwayRead(ctx, lastStreamId, errorCode, payload);
580 }
581
582 private void readWindowUpdateFrame(ChannelHandlerContext ctx, ByteBuf payload,
583 Http2FrameListener listener) throws Http2Exception {
584 int windowSizeIncrement = readUnsignedInt(payload);
585 if (windowSizeIncrement == 0) {
586
587
588 if (streamId == CONNECTION_STREAM_ID) {
589 throw connectionError(PROTOCOL_ERROR,
590 "Received WINDOW_UPDATE with delta 0 for connection stream");
591 } else {
592 throw streamError(streamId, PROTOCOL_ERROR,
593 "Received WINDOW_UPDATE with delta 0 for stream: %d", streamId);
594 }
595 }
596 listener.onWindowUpdateRead(ctx, streamId, windowSizeIncrement);
597 }
598
599 private void readContinuationFrame(ByteBuf payload, Http2FrameListener listener)
600 throws Http2Exception {
601
602 headersContinuation.processFragment(flags.endOfHeaders(), payload,
603 payloadLength, listener);
604 resetHeadersContinuationIfEnd(flags.endOfHeaders());
605 }
606
607 private void readUnknownFrame(ChannelHandlerContext ctx, ByteBuf payload,
608 Http2FrameListener listener) throws Http2Exception {
609 listener.onUnknownFrame(ctx, frameType, streamId, flags, payload);
610 }
611
612
613
614
615
616 private int readPadding(ByteBuf payload) {
617 if (!flags.paddingPresent()) {
618 return 0;
619 }
620 return payload.readUnsignedByte() + 1;
621 }
622
623 private void verifyPadding(int padding) throws Http2Exception {
624 int len = lengthWithoutTrailingPadding(payloadLength, padding);
625 if (len < 0) {
626 throw connectionError(PROTOCOL_ERROR, "Frame payload too small for padding.");
627 }
628 }
629
630
631
632
633
634 private static int lengthWithoutTrailingPadding(int readableBytes, int padding) {
635 return padding == 0
636 ? readableBytes
637 : readableBytes - (padding - 1);
638 }
639
640
641
642
643
644
645 private abstract class HeadersContinuation {
646 private final HeadersBlockBuilder builder = new HeadersBlockBuilder();
647
648
649
650
651 abstract int getStreamId();
652
653
654
655
656
657
658
659
660 abstract void processFragment(boolean endOfHeaders, ByteBuf fragment, int len,
661 Http2FrameListener listener) throws Http2Exception;
662
663 final HeadersBlockBuilder headersBlockBuilder() {
664 return builder;
665 }
666
667
668
669
670 final void close() {
671 builder.close();
672 }
673 }
674
675
676
677
678
679 protected class HeadersBlockBuilder {
680 private ByteBuf headerBlock;
681
682
683
684
685
686 private void headerSizeExceeded() throws Http2Exception {
687 close();
688 headerListSizeExceeded(headersDecoder.configuration().maxHeaderListSizeGoAway());
689 }
690
691
692
693
694
695
696
697
698
699
700 final void addFragment(ByteBuf fragment, int len, ByteBufAllocator alloc,
701 boolean endOfHeaders) throws Http2Exception {
702 if (headerBlock == null) {
703 if (len > headersDecoder.configuration().maxHeaderListSizeGoAway()) {
704 headerSizeExceeded();
705 }
706 if (endOfHeaders) {
707
708
709 headerBlock = fragment.readRetainedSlice(len);
710 } else {
711 headerBlock = alloc.buffer(len).writeBytes(fragment, len);
712 }
713 return;
714 }
715 if (headersDecoder.configuration().maxHeaderListSizeGoAway() - len <
716 headerBlock.readableBytes()) {
717 headerSizeExceeded();
718 }
719 if (headerBlock.isWritable(len)) {
720
721 headerBlock.writeBytes(fragment, len);
722 } else {
723
724 ByteBuf buf = alloc.buffer(headerBlock.readableBytes() + len);
725 buf.writeBytes(headerBlock).writeBytes(fragment, len);
726 headerBlock.release();
727 headerBlock = buf;
728 }
729 }
730
731
732
733
734
735 Http2Headers headers() throws Http2Exception {
736 try {
737 return headersDecoder.decodeHeaders(streamId, headerBlock);
738 } finally {
739 close();
740 }
741 }
742
743
744
745
746 void close() {
747 if (headerBlock != null) {
748 headerBlock.release();
749 headerBlock = null;
750 }
751
752
753 headersContinuation = null;
754 }
755 }
756
757
758
759
760
761 private void verifyNotProcessingHeaders() throws Http2Exception {
762 if (headersContinuation != null) {
763 throw connectionError(PROTOCOL_ERROR, "Received frame of type %s while processing headers on stream %d.",
764 frameType, headersContinuation.getStreamId());
765 }
766 }
767
768 private void verifyAssociatedWithAStream() throws Http2Exception {
769 if (streamId == 0) {
770 throw connectionError(PROTOCOL_ERROR, "Frame of type %s must be associated with a stream.", frameType);
771 }
772 }
773
774 private static void verifyStreamOrConnectionId(int streamId, String argumentName)
775 throws Http2Exception {
776 if (streamId < 0) {
777 throw connectionError(PROTOCOL_ERROR, "%s must be >= 0", argumentName);
778 }
779 }
780 }