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