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