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
403
404
405 int dataLength = lengthWithoutTrailingPadding(payload.readableBytes(), padding);
406
407 payload.writerIndex(payload.readerIndex() + dataLength);
408 listener.onDataRead(ctx, streamId, payload, padding, flags.endOfStream());
409 }
410
411 private void readHeadersFrame(final ChannelHandlerContext ctx, ByteBuf payload,
412 Http2FrameListener listener) throws Http2Exception {
413 final int headersStreamId = streamId;
414 final Http2Flags headersFlags = flags;
415 final int padding = readPadding(payload);
416
417
418
419 if (flags.priorityPresent()) {
420 long word1 = payload.readUnsignedInt();
421 final boolean exclusive = (word1 & 0x80000000L) != 0;
422 final int streamDependency = (int) (word1 & 0x7FFFFFFFL);
423 if (streamDependency == streamId) {
424
425
426
427
428 throw connectionError(
429 PROTOCOL_ERROR, "HEADERS frame for stream %d cannot depend on itself.", streamId);
430 }
431 final short weight = (short) (payload.readUnsignedByte() + 1);
432 final int lenToRead = lengthWithoutTrailingPadding(payload.readableBytes(), padding);
433
434
435 headersContinuation = new HeadersContinuation() {
436 @Override
437 public int getStreamId() {
438 return headersStreamId;
439 }
440
441 @Override
442 public void processFragment(boolean endOfHeaders, ByteBuf fragment, int len,
443 Http2FrameListener listener) throws Http2Exception {
444 final HeadersBlockBuilder hdrBlockBuilder = headersBlockBuilder();
445 hdrBlockBuilder.addFragment(fragment, len, ctx.alloc(), endOfHeaders);
446 if (endOfHeaders) {
447 listener.onHeadersRead(ctx, headersStreamId, hdrBlockBuilder.headers(), streamDependency,
448 weight, exclusive, padding, headersFlags.endOfStream());
449 }
450 }
451 };
452
453
454 headersContinuation.processFragment(flags.endOfHeaders(), payload, lenToRead, listener);
455 resetHeadersContinuationIfEnd(flags.endOfHeaders());
456 return;
457 }
458
459
460
461 headersContinuation = new HeadersContinuation() {
462 @Override
463 public int getStreamId() {
464 return headersStreamId;
465 }
466
467 @Override
468 public void processFragment(boolean endOfHeaders, ByteBuf fragment, int len,
469 Http2FrameListener listener) throws Http2Exception {
470 final HeadersBlockBuilder hdrBlockBuilder = headersBlockBuilder();
471 hdrBlockBuilder.addFragment(fragment, len, ctx.alloc(), endOfHeaders);
472 if (endOfHeaders) {
473 listener.onHeadersRead(ctx, headersStreamId, hdrBlockBuilder.headers(), padding,
474 headersFlags.endOfStream());
475 }
476 }
477 };
478
479
480 int len = lengthWithoutTrailingPadding(payload.readableBytes(), padding);
481 headersContinuation.processFragment(flags.endOfHeaders(), payload, len, listener);
482 resetHeadersContinuationIfEnd(flags.endOfHeaders());
483 }
484
485 private void resetHeadersContinuationIfEnd(boolean endOfHeaders) {
486 if (endOfHeaders) {
487 closeHeadersContinuation();
488 }
489 }
490
491 private void readPriorityFrame(ChannelHandlerContext ctx, ByteBuf payload,
492 Http2FrameListener listener) throws Http2Exception {
493 long word1 = payload.readUnsignedInt();
494 boolean exclusive = (word1 & 0x80000000L) != 0;
495 int streamDependency = (int) (word1 & 0x7FFFFFFFL);
496 if (streamDependency == streamId) {
497 throw streamError(streamId, PROTOCOL_ERROR, "A stream cannot depend on itself.");
498 }
499 short weight = (short) (payload.readUnsignedByte() + 1);
500 listener.onPriorityRead(ctx, streamId, streamDependency, weight, exclusive);
501 }
502
503 private void readRstStreamFrame(ChannelHandlerContext ctx, ByteBuf payload,
504 Http2FrameListener listener) throws Http2Exception {
505 long errorCode = payload.readUnsignedInt();
506 listener.onRstStreamRead(ctx, streamId, errorCode);
507 }
508
509 private void readSettingsFrame(ChannelHandlerContext ctx, ByteBuf payload,
510 Http2FrameListener listener) throws Http2Exception {
511 if (flags.ack()) {
512 listener.onSettingsAckRead(ctx);
513 } else {
514 int numSettings = payloadLength / SETTING_ENTRY_LENGTH;
515 Http2Settings settings = new Http2Settings();
516 for (int index = 0; index < numSettings; ++index) {
517 char id = (char) payload.readUnsignedShort();
518 long value = payload.readUnsignedInt();
519 try {
520 settings.put(id, Long.valueOf(value));
521 } catch (IllegalArgumentException e) {
522 if (id == SETTINGS_INITIAL_WINDOW_SIZE) {
523 throw connectionError(FLOW_CONTROL_ERROR, e,
524 "Failed setting initial window size: %s", e.getMessage());
525 }
526 throw connectionError(PROTOCOL_ERROR, e, "Protocol error: %s", e.getMessage());
527 }
528 }
529 listener.onSettingsRead(ctx, settings);
530 }
531 }
532
533 private void readPushPromiseFrame(final ChannelHandlerContext ctx, ByteBuf payload,
534 Http2FrameListener listener) throws Http2Exception {
535 final int pushPromiseStreamId = streamId;
536 final int padding = readPadding(payload);
537 final int promisedStreamId = readUnsignedInt(payload);
538
539
540 headersContinuation = new HeadersContinuation() {
541 @Override
542 public int getStreamId() {
543 return pushPromiseStreamId;
544 }
545
546 @Override
547 public void processFragment(boolean endOfHeaders, ByteBuf fragment, int len,
548 Http2FrameListener listener) throws Http2Exception {
549 headersBlockBuilder().addFragment(fragment, len, ctx.alloc(), endOfHeaders);
550 if (endOfHeaders) {
551 listener.onPushPromiseRead(ctx, pushPromiseStreamId, promisedStreamId,
552 headersBlockBuilder().headers(), padding);
553 }
554 }
555 };
556
557
558 int len = lengthWithoutTrailingPadding(payload.readableBytes(), padding);
559 headersContinuation.processFragment(flags.endOfHeaders(), payload, len, listener);
560 resetHeadersContinuationIfEnd(flags.endOfHeaders());
561 }
562
563 private void readPingFrame(ChannelHandlerContext ctx, long data,
564 Http2FrameListener listener) throws Http2Exception {
565 if (flags.ack()) {
566 listener.onPingAckRead(ctx, data);
567 } else {
568 listener.onPingRead(ctx, data);
569 }
570 }
571
572 private void readGoAwayFrame(ChannelHandlerContext ctx, ByteBuf payload,
573 Http2FrameListener listener) throws Http2Exception {
574 int lastStreamId = readUnsignedInt(payload);
575 long errorCode = payload.readUnsignedInt();
576 listener.onGoAwayRead(ctx, lastStreamId, errorCode, payload);
577 }
578
579 private void readWindowUpdateFrame(ChannelHandlerContext ctx, ByteBuf payload,
580 Http2FrameListener listener) throws Http2Exception {
581 int windowSizeIncrement = readUnsignedInt(payload);
582 if (windowSizeIncrement == 0) {
583
584
585 if (streamId == CONNECTION_STREAM_ID) {
586 throw connectionError(PROTOCOL_ERROR,
587 "Received WINDOW_UPDATE with delta 0 for connection stream");
588 } else {
589 throw streamError(streamId, PROTOCOL_ERROR,
590 "Received WINDOW_UPDATE with delta 0 for stream: %d", streamId);
591 }
592 }
593 listener.onWindowUpdateRead(ctx, streamId, windowSizeIncrement);
594 }
595
596 private void readContinuationFrame(ByteBuf payload, Http2FrameListener listener)
597 throws Http2Exception {
598
599 headersContinuation.processFragment(flags.endOfHeaders(), payload,
600 payloadLength, listener);
601 resetHeadersContinuationIfEnd(flags.endOfHeaders());
602 }
603
604 private void readUnknownFrame(ChannelHandlerContext ctx, ByteBuf payload,
605 Http2FrameListener listener) throws Http2Exception {
606 listener.onUnknownFrame(ctx, frameType, streamId, flags, payload);
607 }
608
609
610
611
612
613 private int readPadding(ByteBuf payload) {
614 if (!flags.paddingPresent()) {
615 return 0;
616 }
617 return payload.readUnsignedByte() + 1;
618 }
619
620
621
622
623
624 private static int lengthWithoutTrailingPadding(int readableBytes, int padding) throws Http2Exception {
625 if (padding == 0) {
626 return readableBytes;
627 }
628 int n = readableBytes - (padding - 1);
629 if (n < 0) {
630 throw connectionError(PROTOCOL_ERROR, "Frame payload too small for padding.");
631 }
632 return n;
633 }
634
635
636
637
638
639
640 private abstract class HeadersContinuation {
641 private final HeadersBlockBuilder builder = new HeadersBlockBuilder();
642
643
644
645
646 abstract int getStreamId();
647
648
649
650
651
652
653
654
655 abstract void processFragment(boolean endOfHeaders, ByteBuf fragment, int len,
656 Http2FrameListener listener) throws Http2Exception;
657
658 final HeadersBlockBuilder headersBlockBuilder() {
659 return builder;
660 }
661
662
663
664
665 final void close() {
666 builder.close();
667 }
668 }
669
670
671
672
673
674 protected class HeadersBlockBuilder {
675 private ByteBuf headerBlock;
676
677
678
679
680
681 private void headerSizeExceeded() throws Http2Exception {
682 close();
683 headerListSizeExceeded(headersDecoder.configuration().maxHeaderListSizeGoAway());
684 }
685
686
687
688
689
690
691
692
693
694
695 final void addFragment(ByteBuf fragment, int len, ByteBufAllocator alloc,
696 boolean endOfHeaders) throws Http2Exception {
697 if (headerBlock == null) {
698 if (len > headersDecoder.configuration().maxHeaderListSizeGoAway()) {
699 headerSizeExceeded();
700 }
701 if (endOfHeaders) {
702
703
704 headerBlock = fragment.readRetainedSlice(len);
705 } else {
706 headerBlock = alloc.buffer(len).writeBytes(fragment, len);
707 }
708 return;
709 }
710 if (headersDecoder.configuration().maxHeaderListSizeGoAway() - len <
711 headerBlock.readableBytes()) {
712 headerSizeExceeded();
713 }
714 if (headerBlock.isWritable(len)) {
715
716 headerBlock.writeBytes(fragment, len);
717 } else {
718
719 ByteBuf buf = alloc.buffer(headerBlock.readableBytes() + len);
720 buf.writeBytes(headerBlock).writeBytes(fragment, len);
721 headerBlock.release();
722 headerBlock = buf;
723 }
724 }
725
726
727
728
729
730 Http2Headers headers() throws Http2Exception {
731 try {
732 return headersDecoder.decodeHeaders(streamId, headerBlock);
733 } finally {
734 close();
735 }
736 }
737
738
739
740
741 void close() {
742 if (headerBlock != null) {
743 headerBlock.release();
744 headerBlock = null;
745 }
746
747
748 headersContinuation = null;
749 }
750 }
751
752
753
754
755
756 private void verifyNotProcessingHeaders() throws Http2Exception {
757 if (headersContinuation != null) {
758 throw connectionError(PROTOCOL_ERROR, "Received frame of type %s while processing headers on stream %d.",
759 frameType, headersContinuation.getStreamId());
760 }
761 }
762
763 private void verifyAssociatedWithAStream() throws Http2Exception {
764 if (streamId == 0) {
765 throw connectionError(PROTOCOL_ERROR, "Frame of type %s must be associated with a stream.", frameType);
766 }
767 }
768
769 private static void verifyStreamOrConnectionId(int streamId, String argumentName)
770 throws Http2Exception {
771 if (streamId < 0) {
772 throw connectionError(PROTOCOL_ERROR, "%s must be >= 0", argumentName);
773 }
774 }
775 }