1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.handler.codec.http3;
17
18 import io.netty.buffer.ByteBuf;
19 import io.netty.channel.ChannelHandler;
20 import io.netty.channel.ChannelHandlerContext;
21 import io.netty.channel.ChannelInboundHandlerAdapter;
22 import io.netty.channel.socket.ChannelInputShutdownEvent;
23 import io.netty.handler.codec.quic.QuicChannel;
24 import io.netty.handler.codec.quic.QuicStreamChannel;
25 import io.netty.handler.codec.quic.QuicStreamType;
26 import io.netty.util.ReferenceCountUtil;
27 import io.netty.util.concurrent.Future;
28 import io.netty.util.concurrent.GenericFutureListener;
29 import org.jetbrains.annotations.Nullable;
30
31 import java.nio.channels.ClosedChannelException;
32
33 import static io.netty.handler.codec.http3.Http3CodecUtils.closeOnFailure;
34 import static io.netty.handler.codec.http3.Http3CodecUtils.connectionError;
35 import static io.netty.handler.codec.http3.Http3CodecUtils.criticalStreamClosed;
36 import static io.netty.handler.codec.http3.Http3ErrorCode.H3_FRAME_UNEXPECTED;
37 import static io.netty.handler.codec.http3.Http3ErrorCode.H3_ID_ERROR;
38 import static io.netty.handler.codec.http3.Http3ErrorCode.H3_MISSING_SETTINGS;
39 import static io.netty.handler.codec.http3.Http3ErrorCode.QPACK_ENCODER_STREAM_ERROR;
40 import static io.netty.handler.codec.http3.Http3SettingsFrame.HTTP3_SETTINGS_QPACK_BLOCKED_STREAMS;
41 import static io.netty.handler.codec.http3.Http3SettingsFrame.HTTP3_SETTINGS_QPACK_MAX_TABLE_CAPACITY;
42 import static io.netty.handler.codec.http3.QpackUtil.toIntOrThrow;
43 import static io.netty.util.internal.ThrowableUtil.unknownStackTrace;
44
45 final class Http3ControlStreamInboundHandler extends Http3FrameTypeInboundValidationHandler<Http3ControlStreamFrame> {
46 final boolean server;
47 private final ChannelHandler controlFrameHandler;
48 private final QpackEncoder qpackEncoder;
49 private final Http3ControlStreamOutboundHandler remoteControlStreamHandler;
50 private boolean firstFrameRead;
51 private Long receivedGoawayId;
52 private Long receivedMaxPushId;
53
54 Http3ControlStreamInboundHandler(boolean server, @Nullable ChannelHandler controlFrameHandler,
55 QpackEncoder qpackEncoder,
56 Http3ControlStreamOutboundHandler remoteControlStreamHandler) {
57 super(Http3ControlStreamFrame.class);
58 this.server = server;
59 this.controlFrameHandler = controlFrameHandler;
60 this.qpackEncoder = qpackEncoder;
61 this.remoteControlStreamHandler = remoteControlStreamHandler;
62 }
63
64 boolean isServer() {
65 return server;
66 }
67
68 boolean isGoAwayReceived() {
69 return receivedGoawayId != null;
70 }
71
72 long maxPushIdReceived() {
73 return receivedMaxPushId == null ? -1 : receivedMaxPushId;
74 }
75
76 private boolean forwardControlFrames() {
77 return controlFrameHandler != null;
78 }
79
80 @Override
81 public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
82 super.handlerAdded(ctx);
83
84 if (controlFrameHandler != null) {
85 ctx.pipeline().addLast(controlFrameHandler);
86 }
87 }
88
89 @Override
90 void readFrameDiscarded(ChannelHandlerContext ctx, Object discardedFrame) {
91 if (!firstFrameRead && !(discardedFrame instanceof Http3SettingsFrame)) {
92 connectionError(ctx, Http3ErrorCode.H3_MISSING_SETTINGS, "Missing settings frame.", forwardControlFrames());
93 }
94 }
95
96 @Override
97 void channelRead(ChannelHandlerContext ctx, Http3ControlStreamFrame frame) throws QpackException {
98 boolean isSettingsFrame = frame instanceof Http3SettingsFrame;
99 if (!firstFrameRead && !isSettingsFrame) {
100 connectionError(ctx, H3_MISSING_SETTINGS, "Missing settings frame.", forwardControlFrames());
101 ReferenceCountUtil.release(frame);
102 return;
103 }
104 if (firstFrameRead && isSettingsFrame) {
105 connectionError(ctx, H3_FRAME_UNEXPECTED, "Second settings frame received.", forwardControlFrames());
106 ReferenceCountUtil.release(frame);
107 return;
108 }
109 firstFrameRead = true;
110
111 final boolean valid;
112 if (isSettingsFrame) {
113 valid = handleHttp3SettingsFrame(ctx, (Http3SettingsFrame) frame);
114 } else if (frame instanceof Http3GoAwayFrame) {
115 valid = handleHttp3GoAwayFrame(ctx, (Http3GoAwayFrame) frame);
116 } else if (frame instanceof Http3MaxPushIdFrame) {
117 valid = handleHttp3MaxPushIdFrame(ctx, (Http3MaxPushIdFrame) frame);
118 } else if (frame instanceof Http3CancelPushFrame) {
119 valid = handleHttp3CancelPushFrame(ctx, (Http3CancelPushFrame) frame);
120 } else {
121
122
123 assert frame instanceof Http3UnknownFrame;
124 valid = true;
125 }
126
127 if (!valid || controlFrameHandler == null) {
128 ReferenceCountUtil.release(frame);
129 return;
130 }
131
132
133
134 ctx.fireChannelRead(frame);
135 }
136
137 private boolean handleHttp3SettingsFrame(ChannelHandlerContext ctx, Http3SettingsFrame settingsFrame)
138 throws QpackException {
139 final QuicChannel quicChannel = (QuicChannel) ctx.channel().parent();
140 final QpackAttributes qpackAttributes = Http3.getQpackAttributes(quicChannel);
141 assert qpackAttributes != null;
142 final GenericFutureListener<Future<? super QuicStreamChannel>> closeOnFailure = future -> {
143 if (!future.isSuccess()) {
144 criticalStreamClosed(ctx);
145 }
146 };
147 if (qpackAttributes.dynamicTableDisabled()) {
148 qpackEncoder.configureDynamicTable(qpackAttributes, 0, 0);
149 return true;
150 }
151 quicChannel.createStream(QuicStreamType.UNIDIRECTIONAL,
152 new QPackEncoderStreamInitializer(qpackEncoder, qpackAttributes,
153 settingsFrame.getOrDefault(HTTP3_SETTINGS_QPACK_MAX_TABLE_CAPACITY, 0),
154 settingsFrame.getOrDefault(HTTP3_SETTINGS_QPACK_BLOCKED_STREAMS, 0)))
155 .addListener(closeOnFailure);
156 quicChannel.createStream(QuicStreamType.UNIDIRECTIONAL, new QPackDecoderStreamInitializer(qpackAttributes))
157 .addListener(closeOnFailure);
158 return true;
159 }
160
161 private boolean handleHttp3GoAwayFrame(ChannelHandlerContext ctx, Http3GoAwayFrame goAwayFrame) {
162 long id = goAwayFrame.id();
163 if (!server && id % 4 != 0) {
164 connectionError(ctx, H3_FRAME_UNEXPECTED, "GOAWAY received with ID of non-request stream.",
165 forwardControlFrames());
166 return false;
167 }
168 if (receivedGoawayId != null && id > receivedGoawayId) {
169 connectionError(ctx, H3_ID_ERROR,
170 "GOAWAY received with ID larger than previously received.", forwardControlFrames());
171 return false;
172 }
173 receivedGoawayId = id;
174 return true;
175 }
176
177 private boolean handleHttp3MaxPushIdFrame(ChannelHandlerContext ctx, Http3MaxPushIdFrame frame) {
178 long id = frame.id();
179 if (!server) {
180 connectionError(ctx, H3_FRAME_UNEXPECTED, "MAX_PUSH_ID received by client.",
181 forwardControlFrames());
182 return false;
183 }
184 if (receivedMaxPushId != null && id < receivedMaxPushId) {
185 connectionError(ctx, H3_ID_ERROR, "MAX_PUSH_ID reduced limit.", forwardControlFrames());
186 return false;
187 }
188 receivedMaxPushId = id;
189 return true;
190 }
191
192 private boolean handleHttp3CancelPushFrame(ChannelHandlerContext ctx, Http3CancelPushFrame cancelPushFrame) {
193 final Long maxPushId = server ? receivedMaxPushId : remoteControlStreamHandler.sentMaxPushId();
194 if (maxPushId == null || maxPushId < cancelPushFrame.id()) {
195 connectionError(ctx, H3_ID_ERROR, "CANCEL_PUSH received with an ID greater than MAX_PUSH_ID.",
196 forwardControlFrames());
197 return false;
198 }
199 return true;
200 }
201
202 @Override
203 public void channelReadComplete(ChannelHandlerContext ctx) {
204 ctx.fireChannelReadComplete();
205
206
207
208 Http3CodecUtils.readIfNoAutoRead(ctx);
209 }
210
211 @Override
212 public boolean isSharable() {
213
214 return false;
215 }
216
217 @Override
218 public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
219 if (evt instanceof ChannelInputShutdownEvent) {
220
221 criticalStreamClosed(ctx);
222 }
223 ctx.fireUserEventTriggered(evt);
224 }
225
226 private abstract static class AbstractQPackStreamInitializer extends ChannelInboundHandlerAdapter {
227 private final int streamType;
228 protected final QpackAttributes attributes;
229
230 AbstractQPackStreamInitializer(int streamType, QpackAttributes attributes) {
231 this.streamType = streamType;
232 this.attributes = attributes;
233 }
234
235 @Override
236 public final void channelActive(ChannelHandlerContext ctx) {
237
238
239
240 ByteBuf buffer = ctx.alloc().buffer(8);
241 Http3CodecUtils.writeVariableLengthInteger(buffer, streamType);
242 closeOnFailure(ctx.writeAndFlush(buffer));
243 streamAvailable(ctx);
244 ctx.fireChannelActive();
245 }
246
247 @Override
248 public final void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
249 streamClosed(ctx);
250 if (evt instanceof ChannelInputShutdownEvent) {
251
252 criticalStreamClosed(ctx);
253 }
254 ctx.fireUserEventTriggered(evt);
255 }
256
257 @Override
258 public void channelInactive(ChannelHandlerContext ctx) {
259 streamClosed(ctx);
260
261 criticalStreamClosed(ctx);
262 ctx.fireChannelInactive();
263 }
264
265 protected abstract void streamAvailable(ChannelHandlerContext ctx);
266
267 protected abstract void streamClosed(ChannelHandlerContext ctx);
268 }
269
270 private static final class QPackEncoderStreamInitializer extends AbstractQPackStreamInitializer {
271 private static final ClosedChannelException ENCODER_STREAM_INACTIVE =
272 unknownStackTrace(new ClosedChannelException(), ClosedChannelException.class, "streamClosed()");
273 private final QpackEncoder encoder;
274 private final long maxTableCapacity;
275 private final long blockedStreams;
276
277 QPackEncoderStreamInitializer(QpackEncoder encoder, QpackAttributes attributes, long maxTableCapacity,
278 long blockedStreams) {
279 super(Http3CodecUtils.HTTP3_QPACK_ENCODER_STREAM_TYPE, attributes);
280 this.encoder = encoder;
281 this.maxTableCapacity = maxTableCapacity;
282 this.blockedStreams = blockedStreams;
283 }
284
285 @Override
286 protected void streamAvailable(ChannelHandlerContext ctx) {
287 final QuicStreamChannel stream = (QuicStreamChannel) ctx.channel();
288 attributes.encoderStream(stream);
289
290 try {
291 encoder.configureDynamicTable(attributes, maxTableCapacity, toIntOrThrow(blockedStreams));
292 } catch (QpackException e) {
293 connectionError(ctx, new Http3Exception(QPACK_ENCODER_STREAM_ERROR,
294 "Dynamic table configuration failed.", e), true);
295 }
296 }
297
298 @Override
299 protected void streamClosed(ChannelHandlerContext ctx) {
300 attributes.encoderStreamInactive(ENCODER_STREAM_INACTIVE);
301 }
302 }
303
304 private static final class QPackDecoderStreamInitializer extends AbstractQPackStreamInitializer {
305 private static final ClosedChannelException DECODER_STREAM_INACTIVE =
306 unknownStackTrace(new ClosedChannelException(), ClosedChannelException.class, "streamClosed()");
307 private QPackDecoderStreamInitializer(QpackAttributes attributes) {
308 super(Http3CodecUtils.HTTP3_QPACK_DECODER_STREAM_TYPE, attributes);
309 }
310
311 @Override
312 protected void streamAvailable(ChannelHandlerContext ctx) {
313 attributes.decoderStream((QuicStreamChannel) ctx.channel());
314 }
315
316 @Override
317 protected void streamClosed(ChannelHandlerContext ctx) {
318 attributes.decoderStreamInactive(DECODER_STREAM_INACTIVE);
319 }
320 }
321 }