1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.handler.codec.http2;
17
18 import io.netty.buffer.ByteBuf;
19 import io.netty.channel.Channel;
20 import io.netty.channel.ChannelFuture;
21 import io.netty.channel.ChannelFutureListener;
22 import io.netty.channel.ChannelHandlerContext;
23 import io.netty.channel.ChannelInboundHandler;
24 import io.netty.channel.ChannelPromise;
25 import io.netty.handler.codec.UnsupportedMessageTypeException;
26 import io.netty.handler.codec.http.HttpServerUpgradeHandler.UpgradeEvent;
27 import io.netty.handler.codec.http2.Http2Connection.PropertyKey;
28 import io.netty.handler.codec.http2.Http2Stream.State;
29 import io.netty.handler.codec.http2.StreamBufferingEncoder.Http2ChannelClosedException;
30 import io.netty.handler.codec.http2.StreamBufferingEncoder.Http2GoAwayException;
31 import io.netty.util.ReferenceCountUtil;
32 import io.netty.util.ReferenceCounted;
33 import io.netty.util.collection.IntObjectHashMap;
34 import io.netty.util.collection.IntObjectMap;
35 import io.netty.util.internal.logging.InternalLogger;
36 import io.netty.util.internal.logging.InternalLoggerFactory;
37
38 import static io.netty.buffer.ByteBufUtil.writeAscii;
39 import static io.netty.handler.codec.http2.Http2CodecUtil.HTTP_UPGRADE_STREAM_ID;
40 import static io.netty.handler.codec.http2.Http2CodecUtil.isStreamIdValid;
41 import static io.netty.handler.codec.http2.Http2Error.NO_ERROR;
42 import static io.netty.util.internal.logging.InternalLogLevel.DEBUG;
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146 public class Http2FrameCodec extends Http2ConnectionHandler {
147
148 private static final InternalLogger LOG = InternalLoggerFactory.getInstance(Http2FrameCodec.class);
149
150 private static final Class<?>[] SUPPORTED_MESSAGES = new Class[] {
151 Http2DataFrame.class, Http2HeadersFrame.class, Http2WindowUpdateFrame.class, Http2ResetFrame.class,
152 Http2PingFrame.class, Http2SettingsFrame.class, Http2SettingsAckFrame.class, Http2GoAwayFrame.class,
153 Http2PushPromiseFrame.class, Http2PriorityFrame.class, Http2UnknownFrame.class };
154
155 protected final PropertyKey streamKey;
156 private final PropertyKey upgradeKey;
157
158 private final Integer initialFlowControlWindowSize;
159
160 ChannelHandlerContext ctx;
161
162
163
164
165 private int numBufferedStreams;
166 private final IntObjectMap<DefaultHttp2FrameStream> frameStreamToInitializeMap =
167 new IntObjectHashMap<DefaultHttp2FrameStream>(8);
168
169 Http2FrameCodec(Http2ConnectionEncoder encoder, Http2ConnectionDecoder decoder, Http2Settings initialSettings,
170 boolean decoupleCloseAndGoAway, boolean flushPreface) {
171 super(decoder, encoder, initialSettings, decoupleCloseAndGoAway, flushPreface);
172
173 decoder.frameListener(new FrameListener());
174 connection().addListener(new ConnectionListener());
175 connection().remote().flowController().listener(new Http2RemoteFlowControllerListener());
176 streamKey = connection().newKey();
177 upgradeKey = connection().newKey();
178 initialFlowControlWindowSize = initialSettings.initialWindowSize();
179 }
180
181
182
183
184 DefaultHttp2FrameStream newStream() {
185 return new DefaultHttp2FrameStream();
186 }
187
188
189
190
191
192
193 final void forEachActiveStream(final Http2FrameStreamVisitor streamVisitor) throws Http2Exception {
194 assert ctx.executor().inEventLoop();
195 if (connection().numActiveStreams() > 0) {
196 connection().forEachActiveStream(new Http2StreamVisitor() {
197 @Override
198 public boolean visit(Http2Stream stream) {
199 try {
200 return streamVisitor.visit((Http2FrameStream) stream.getProperty(streamKey));
201 } catch (Throwable cause) {
202 onError(ctx, false, cause);
203 return false;
204 }
205 }
206 });
207 }
208 }
209
210
211
212
213
214
215 int numInitializingStreams() {
216 return frameStreamToInitializeMap.size();
217 }
218
219 @Override
220 public final void handlerAdded(ChannelHandlerContext ctx) throws Exception {
221 this.ctx = ctx;
222 super.handlerAdded(ctx);
223 handlerAdded0(ctx);
224
225
226 Http2Connection connection = connection();
227 if (connection.isServer()) {
228 tryExpandConnectionFlowControlWindow(connection);
229 }
230 }
231
232 private void tryExpandConnectionFlowControlWindow(Http2Connection connection) throws Http2Exception {
233 if (initialFlowControlWindowSize != null) {
234
235
236 Http2Stream connectionStream = connection.connectionStream();
237 Http2LocalFlowController localFlowController = connection.local().flowController();
238 final int delta = initialFlowControlWindowSize - localFlowController.initialWindowSize(connectionStream);
239
240 if (delta > 0) {
241
242 localFlowController.incrementWindowSize(connectionStream, Math.max(delta << 1, delta));
243 flush(ctx);
244 }
245 }
246 }
247
248 void handlerAdded0(@SuppressWarnings("unsed") ChannelHandlerContext ctx) throws Exception {
249
250 }
251
252
253
254
255
256 @Override
257 public final void userEventTriggered(final ChannelHandlerContext ctx, final Object evt) throws Exception {
258 if (evt == Http2ConnectionPrefaceAndSettingsFrameWrittenEvent.INSTANCE) {
259
260 tryExpandConnectionFlowControlWindow(connection());
261
262
263
264
265 ctx.executor().execute(new Runnable() {
266 @Override
267 public void run() {
268 ctx.fireUserEventTriggered(evt);
269 }
270 });
271 } else if (evt instanceof UpgradeEvent) {
272 UpgradeEvent upgrade = (UpgradeEvent) evt;
273 try {
274 onUpgradeEvent(ctx, upgrade.retain());
275 Http2Stream stream = connection().stream(HTTP_UPGRADE_STREAM_ID);
276 if (stream.getProperty(streamKey) == null) {
277
278
279
280 onStreamActive0(stream);
281 }
282 upgrade.upgradeRequest().headers().setInt(
283 HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), HTTP_UPGRADE_STREAM_ID);
284 stream.setProperty(upgradeKey, true);
285 InboundHttpToHttp2Adapter.handle(
286 ctx, connection(), decoder().frameListener(), upgrade.upgradeRequest().retain());
287 } finally {
288 upgrade.release();
289 }
290 } else {
291 onUserEventTriggered(ctx, evt);
292 ctx.fireUserEventTriggered(evt);
293 }
294 }
295
296 void onUserEventTriggered(final ChannelHandlerContext ctx, final Object evt) throws Exception {
297
298 }
299
300
301
302
303
304 @Override
305 public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
306 if (msg instanceof Http2DataFrame) {
307 Http2DataFrame dataFrame = (Http2DataFrame) msg;
308 encoder().writeData(ctx, dataFrame.stream().id(), dataFrame.content(),
309 dataFrame.padding(), dataFrame.isEndStream(), promise);
310 } else if (msg instanceof Http2HeadersFrame) {
311 writeHeadersFrame(ctx, (Http2HeadersFrame) msg, promise);
312 } else if (msg instanceof Http2WindowUpdateFrame) {
313 Http2WindowUpdateFrame frame = (Http2WindowUpdateFrame) msg;
314 Http2FrameStream frameStream = frame.stream();
315
316
317 try {
318 if (frameStream == null) {
319 increaseInitialConnectionWindow(frame.windowSizeIncrement());
320 } else {
321 consumeBytes(frameStream.id(), frame.windowSizeIncrement());
322 }
323 promise.setSuccess();
324 } catch (Throwable t) {
325 promise.setFailure(t);
326 }
327 } else if (msg instanceof Http2ResetFrame) {
328 Http2ResetFrame rstFrame = (Http2ResetFrame) msg;
329 int id = rstFrame.stream().id();
330
331
332 if (connection().streamMayHaveExisted(id)) {
333 encoder().writeRstStream(ctx, rstFrame.stream().id(), rstFrame.errorCode(), promise);
334 } else {
335 ReferenceCountUtil.release(rstFrame);
336 promise.setFailure(Http2Exception.streamError(
337 rstFrame.stream().id(), Http2Error.PROTOCOL_ERROR, "Stream never existed"));
338 }
339 } else if (msg instanceof Http2PingFrame) {
340 Http2PingFrame frame = (Http2PingFrame) msg;
341 encoder().writePing(ctx, frame.ack(), frame.content(), promise);
342 } else if (msg instanceof Http2SettingsFrame) {
343 encoder().writeSettings(ctx, ((Http2SettingsFrame) msg).settings(), promise);
344 } else if (msg instanceof Http2SettingsAckFrame) {
345
346
347 encoder().writeSettingsAck(ctx, promise);
348 } else if (msg instanceof Http2GoAwayFrame) {
349 writeGoAwayFrame(ctx, (Http2GoAwayFrame) msg, promise);
350 } else if (msg instanceof Http2PushPromiseFrame) {
351 Http2PushPromiseFrame pushPromiseFrame = (Http2PushPromiseFrame) msg;
352 writePushPromise(ctx, pushPromiseFrame, promise);
353 } else if (msg instanceof Http2PriorityFrame) {
354 Http2PriorityFrame priorityFrame = (Http2PriorityFrame) msg;
355 encoder().writePriority(ctx, priorityFrame.stream().id(), priorityFrame.streamDependency(),
356 priorityFrame.weight(), priorityFrame.exclusive(), promise);
357 } else if (msg instanceof Http2UnknownFrame) {
358 Http2UnknownFrame unknownFrame = (Http2UnknownFrame) msg;
359 encoder().writeFrame(ctx, unknownFrame.frameType(), unknownFrame.stream().id(),
360 unknownFrame.flags(), unknownFrame.content(), promise);
361 } else if (!(msg instanceof Http2Frame)) {
362 ctx.write(msg, promise);
363 } else {
364 ReferenceCountUtil.release(msg);
365 throw new UnsupportedMessageTypeException(msg, SUPPORTED_MESSAGES);
366 }
367 }
368
369 private void increaseInitialConnectionWindow(int deltaBytes) throws Http2Exception {
370
371 connection().local().flowController().incrementWindowSize(connection().connectionStream(), deltaBytes);
372 }
373
374 final boolean consumeBytes(int streamId, int bytes) throws Http2Exception {
375 Http2Stream stream = connection().stream(streamId);
376
377
378 if (stream != null && streamId == Http2CodecUtil.HTTP_UPGRADE_STREAM_ID) {
379 Boolean upgraded = stream.getProperty(upgradeKey);
380 if (Boolean.TRUE.equals(upgraded)) {
381 return false;
382 }
383 }
384
385 return connection().local().flowController().consumeBytes(stream, bytes);
386 }
387
388 private void writeGoAwayFrame(ChannelHandlerContext ctx, Http2GoAwayFrame frame, ChannelPromise promise) {
389 if (frame.lastStreamId() > -1) {
390 frame.release();
391 throw new IllegalArgumentException("Last stream id must not be set on GOAWAY frame");
392 }
393
394 int lastStreamCreated = connection().remote().lastStreamCreated();
395 long lastStreamId = lastStreamCreated + ((long) frame.extraStreamIds()) * 2;
396
397 if (lastStreamId > Integer.MAX_VALUE) {
398 lastStreamId = Integer.MAX_VALUE;
399 }
400 goAway(ctx, (int) lastStreamId, frame.errorCode(), frame.content(), promise);
401 }
402
403 private void writeHeadersFrame(final ChannelHandlerContext ctx, Http2HeadersFrame headersFrame,
404 ChannelPromise promise) {
405
406 if (isStreamIdValid(headersFrame.stream().id())) {
407 encoder().writeHeaders(ctx, headersFrame.stream().id(), headersFrame.headers(), headersFrame.padding(),
408 headersFrame.isEndStream(), promise);
409 } else if (initializeNewStream(ctx, (DefaultHttp2FrameStream) headersFrame.stream(), promise)) {
410 promise = promise.unvoid();
411
412 final int streamId = headersFrame.stream().id();
413
414 encoder().writeHeaders(ctx, streamId, headersFrame.headers(), headersFrame.padding(),
415 headersFrame.isEndStream(), promise);
416
417 if (!promise.isDone()) {
418 numBufferedStreams++;
419
420
421 promise.addListener(new ChannelFutureListener() {
422 @Override
423 public void operationComplete(ChannelFuture channelFuture) {
424 numBufferedStreams--;
425 handleHeaderFuture(channelFuture, streamId);
426 }
427 });
428 } else {
429 handleHeaderFuture(promise, streamId);
430 }
431 }
432 }
433
434 private void writePushPromise(final ChannelHandlerContext ctx, Http2PushPromiseFrame pushPromiseFrame,
435 final ChannelPromise promise) {
436 if (isStreamIdValid(pushPromiseFrame.pushStream().id())) {
437 encoder().writePushPromise(ctx, pushPromiseFrame.stream().id(), pushPromiseFrame.pushStream().id(),
438 pushPromiseFrame.http2Headers(), pushPromiseFrame.padding(), promise);
439 } else if (initializeNewStream(ctx, (DefaultHttp2FrameStream) pushPromiseFrame.pushStream(), promise)) {
440 final int streamId = pushPromiseFrame.stream().id();
441 encoder().writePushPromise(ctx, streamId, pushPromiseFrame.pushStream().id(),
442 pushPromiseFrame.http2Headers(), pushPromiseFrame.padding(), promise);
443
444 if (promise.isDone()) {
445 handleHeaderFuture(promise, streamId);
446 } else {
447 numBufferedStreams++;
448
449
450 promise.addListener(new ChannelFutureListener() {
451 @Override
452 public void operationComplete(ChannelFuture channelFuture) {
453 numBufferedStreams--;
454 handleHeaderFuture(channelFuture, streamId);
455 }
456 });
457 }
458 }
459 }
460
461 private boolean initializeNewStream(ChannelHandlerContext ctx, DefaultHttp2FrameStream http2FrameStream,
462 ChannelPromise promise) {
463 final Http2Connection connection = connection();
464 final int streamId = connection.local().incrementAndGetNextStreamId();
465 if (streamId < 0) {
466 promise.setFailure(new Http2NoMoreStreamIdsException());
467
468
469
470 onHttp2Frame(ctx, new DefaultHttp2GoAwayFrame(connection.isServer() ? Integer.MAX_VALUE :
471 Integer.MAX_VALUE - 1, NO_ERROR.code(),
472 writeAscii(ctx.alloc(), "Stream IDs exhausted on local stream creation")));
473
474 return false;
475 }
476 http2FrameStream.id = streamId;
477
478
479
480
481
482
483 Object old = frameStreamToInitializeMap.put(streamId, http2FrameStream);
484
485
486 assert old == null;
487 return true;
488 }
489
490 private void handleHeaderFuture(ChannelFuture channelFuture, int streamId) {
491 if (!channelFuture.isSuccess()) {
492 frameStreamToInitializeMap.remove(streamId);
493 }
494 }
495
496 private void onStreamActive0(Http2Stream stream) {
497 if (stream.id() != Http2CodecUtil.HTTP_UPGRADE_STREAM_ID &&
498 connection().local().isValidStreamId(stream.id())) {
499 return;
500 }
501
502 DefaultHttp2FrameStream stream2 = newStream().setStreamAndProperty(streamKey, stream);
503 onHttp2StreamStateChanged(ctx, stream2);
504 }
505
506 private final class ConnectionListener extends Http2ConnectionAdapter {
507 @Override
508 public void onStreamAdded(Http2Stream stream) {
509 DefaultHttp2FrameStream frameStream = frameStreamToInitializeMap.remove(stream.id());
510
511 if (frameStream != null) {
512 frameStream.setStreamAndProperty(streamKey, stream);
513 }
514 }
515
516 @Override
517 public void onStreamActive(Http2Stream stream) {
518 onStreamActive0(stream);
519 }
520
521 @Override
522 public void onStreamClosed(Http2Stream stream) {
523 onHttp2StreamStateChanged0(stream);
524 }
525
526 @Override
527 public void onStreamHalfClosed(Http2Stream stream) {
528 onHttp2StreamStateChanged0(stream);
529 }
530
531 private void onHttp2StreamStateChanged0(Http2Stream stream) {
532 DefaultHttp2FrameStream stream2 = stream.getProperty(streamKey);
533 if (stream2 != null) {
534 onHttp2StreamStateChanged(ctx, stream2);
535 }
536 }
537 }
538
539 @Override
540 protected void onConnectionError(
541 ChannelHandlerContext ctx, boolean outbound, Throwable cause, Http2Exception http2Ex) {
542 if (!outbound) {
543
544
545
546
547 ctx.fireExceptionCaught(cause);
548 }
549 super.onConnectionError(ctx, outbound, cause, http2Ex);
550 }
551
552
553
554
555
556 @Override
557 protected final void onStreamError(ChannelHandlerContext ctx, boolean outbound, Throwable cause,
558 Http2Exception.StreamException streamException) {
559 int streamId = streamException.streamId();
560 Http2Stream connectionStream = connection().stream(streamId);
561 if (connectionStream == null) {
562 onHttp2UnknownStreamError(ctx, cause, streamException);
563
564 super.onStreamError(ctx, outbound, cause, streamException);
565 return;
566 }
567
568 Http2FrameStream stream = connectionStream.getProperty(streamKey);
569 if (stream == null) {
570 LOG.warn("Stream exception thrown without stream object attached.", cause);
571
572 super.onStreamError(ctx, outbound, cause, streamException);
573 return;
574 }
575
576 if (!outbound) {
577
578 onHttp2FrameStreamException(ctx, new Http2FrameStreamException(stream, streamException.error(), cause));
579 }
580 }
581
582 private static void onHttp2UnknownStreamError(@SuppressWarnings("unused") ChannelHandlerContext ctx,
583 Throwable cause, Http2Exception.StreamException streamException) {
584
585
586
587
588
589 LOG.log(DEBUG, "Stream exception thrown for unknown stream {}.", streamException.streamId(), cause);
590 }
591
592 @Override
593 protected final boolean isGracefulShutdownComplete() {
594 return super.isGracefulShutdownComplete() && numBufferedStreams == 0;
595 }
596
597 private final class FrameListener implements Http2FrameListener {
598
599 @Override
600 public void onUnknownFrame(
601 ChannelHandlerContext ctx, byte frameType, int streamId, Http2Flags flags, ByteBuf payload) {
602 if (streamId == 0) {
603
604 return;
605 }
606 onHttp2Frame(ctx, new DefaultHttp2UnknownFrame(frameType, flags, payload)
607 .stream(requireStream(streamId)).retain());
608 }
609
610 @Override
611 public void onSettingsRead(ChannelHandlerContext ctx, Http2Settings settings) {
612 onHttp2Frame(ctx, new DefaultHttp2SettingsFrame(settings));
613 }
614
615 @Override
616 public void onPingRead(ChannelHandlerContext ctx, long data) {
617 onHttp2Frame(ctx, new DefaultHttp2PingFrame(data, false));
618 }
619
620 @Override
621 public void onPingAckRead(ChannelHandlerContext ctx, long data) {
622 onHttp2Frame(ctx, new DefaultHttp2PingFrame(data, true));
623 }
624
625 @Override
626 public void onRstStreamRead(ChannelHandlerContext ctx, int streamId, long errorCode) {
627 onHttp2Frame(ctx, new DefaultHttp2ResetFrame(errorCode).stream(requireStream(streamId)));
628 }
629
630 @Override
631 public void onWindowUpdateRead(ChannelHandlerContext ctx, int streamId, int windowSizeIncrement) {
632 if (streamId == 0) {
633
634 return;
635 }
636 onHttp2Frame(ctx, new DefaultHttp2WindowUpdateFrame(windowSizeIncrement).stream(requireStream(streamId)));
637 }
638
639 @Override
640 public void onHeadersRead(ChannelHandlerContext ctx, int streamId,
641 Http2Headers headers, int streamDependency, short weight, boolean
642 exclusive, int padding, boolean endStream) {
643 onHeadersRead(ctx, streamId, headers, padding, endStream);
644 }
645
646 @Override
647 public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
648 int padding, boolean endOfStream) {
649 onHttp2Frame(ctx, new DefaultHttp2HeadersFrame(headers, endOfStream, padding)
650 .stream(requireStream(streamId)));
651 }
652
653 @Override
654 public int onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
655 boolean endOfStream) {
656 onHttp2Frame(ctx, new DefaultHttp2DataFrame(data, endOfStream, padding)
657 .stream(requireStream(streamId)).retain());
658
659 return 0;
660 }
661
662 @Override
663 public void onGoAwayRead(ChannelHandlerContext ctx, int lastStreamId, long errorCode, ByteBuf debugData) {
664 onHttp2Frame(ctx, new DefaultHttp2GoAwayFrame(lastStreamId, errorCode, debugData).retain());
665 }
666
667 @Override
668 public void onPriorityRead(ChannelHandlerContext ctx, int streamId, int streamDependency,
669 short weight, boolean exclusive) {
670
671 Http2Stream stream = connection().stream(streamId);
672 if (stream == null) {
673
674 return;
675 }
676 onHttp2Frame(ctx, new DefaultHttp2PriorityFrame(streamDependency, weight, exclusive)
677 .stream(requireStream(streamId)));
678 }
679
680 @Override
681 public void onSettingsAckRead(ChannelHandlerContext ctx) {
682 onHttp2Frame(ctx, Http2SettingsAckFrame.INSTANCE);
683 }
684
685 @Override
686 public void onPushPromiseRead(ChannelHandlerContext ctx, int streamId, int promisedStreamId,
687 Http2Headers headers, int padding) {
688 onHttp2Frame(ctx, new DefaultHttp2PushPromiseFrame(headers, padding, promisedStreamId)
689 .pushStream(new DefaultHttp2FrameStream()
690 .setStreamAndProperty(streamKey, connection().stream(promisedStreamId)))
691 .stream(requireStream(streamId)));
692 }
693
694 private Http2FrameStream requireStream(int streamId) {
695 Http2FrameStream stream = connection().stream(streamId).getProperty(streamKey);
696 if (stream == null) {
697 throw new IllegalStateException("Stream object required for identifier: " + streamId);
698 }
699 return stream;
700 }
701 }
702
703 private void onUpgradeEvent(ChannelHandlerContext ctx, UpgradeEvent evt) {
704 ctx.fireUserEventTriggered(evt);
705 }
706
707 private void onHttp2StreamWritabilityChanged(ChannelHandlerContext ctx, DefaultHttp2FrameStream stream,
708 @SuppressWarnings("unused") boolean writable) {
709 ctx.fireUserEventTriggered(stream.writabilityChanged);
710 }
711
712 void onHttp2StreamStateChanged(ChannelHandlerContext ctx, DefaultHttp2FrameStream stream) {
713 ctx.fireUserEventTriggered(stream.stateChanged);
714 }
715
716 void onHttp2Frame(ChannelHandlerContext ctx, Http2Frame frame) {
717 ctx.fireChannelRead(frame);
718 }
719
720 void onHttp2FrameStreamException(ChannelHandlerContext ctx, Http2FrameStreamException cause) {
721 ctx.fireExceptionCaught(cause);
722 }
723
724 private final class Http2RemoteFlowControllerListener implements Http2RemoteFlowController.Listener {
725 @Override
726 public void writabilityChanged(Http2Stream stream) {
727 DefaultHttp2FrameStream frameStream = stream.getProperty(streamKey);
728 if (frameStream == null) {
729 return;
730 }
731 onHttp2StreamWritabilityChanged(
732 ctx, frameStream, connection().remote().flowController().isWritable(stream));
733 }
734 }
735
736
737
738
739
740 static class DefaultHttp2FrameStream implements Http2FrameStream {
741
742 private volatile int id = -1;
743 private volatile Http2Stream stream;
744
745 final Http2FrameStreamEvent stateChanged = Http2FrameStreamEvent.stateChanged(this);
746 final Http2FrameStreamEvent writabilityChanged = Http2FrameStreamEvent.writabilityChanged(this);
747
748 Channel attachment;
749
750 DefaultHttp2FrameStream setStreamAndProperty(PropertyKey streamKey, Http2Stream stream) {
751 assert id == -1 || stream.id() == id;
752 this.stream = stream;
753 stream.setProperty(streamKey, this);
754 return this;
755 }
756
757 @Override
758 public int id() {
759 Http2Stream stream = this.stream;
760 return stream == null ? id : stream.id();
761 }
762
763 @Override
764 public State state() {
765 Http2Stream stream = this.stream;
766 return stream == null ? State.IDLE : stream.state();
767 }
768
769 @Override
770 public String toString() {
771 return String.valueOf(id());
772 }
773 }
774 }