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.ChannelHandlerContext;
20 import io.netty.channel.socket.ChannelInputShutdownEvent;
21 import io.netty.handler.codec.ByteToMessageDecoder;
22 import io.netty.handler.codec.quic.QuicStreamChannel;
23 import io.netty.util.AsciiString;
24 import org.jetbrains.annotations.Nullable;
25
26 import java.util.List;
27
28 import static io.netty.handler.codec.http3.Http3CodecUtils.connectionError;
29 import static io.netty.handler.codec.http3.Http3ErrorCode.QPACK_ENCODER_STREAM_ERROR;
30 import static io.netty.handler.codec.http3.QpackUtil.MAX_UNSIGNED_INT;
31 import static io.netty.handler.codec.http3.QpackUtil.decodePrefixedIntegerAsInt;
32 import static io.netty.util.internal.ObjectUtil.checkInRange;
33
34 final class QpackEncoderHandler extends ByteToMessageDecoder {
35 private static final QpackException INVALID_LENGTH_STRING_LITERAL =
36 QpackException.newStatic(QpackEncoderHandler.class, "decodeStringLiteral(...)",
37 "QPACK - invalid length for STRING_LITERAL");
38 private static final QpackException STRING_LITERAL_TOO_LARGE =
39 QpackException.newStatic(QpackEncoderHandler.class, "checkStringLiteralLength(...)",
40 "QPACK - string literal exceeds the maximum dynamic table capacity");
41 private final QpackHuffmanDecoder huffmanDecoder;
42 private final QpackDecoder qpackDecoder;
43
44
45
46 private final long maxTableCapacity;
47
48
49
50 @Nullable
51 private CharSequence pendingName;
52 private boolean discard;
53
54 QpackEncoderHandler(@Nullable Long maxTableCapacity, QpackDecoder qpackDecoder) {
55 this(maxTableCapacity, qpackDecoder, new QpackHuffmanDecoder());
56 }
57
58 QpackEncoderHandler(@Nullable Long maxTableCapacity, QpackDecoder qpackDecoder,
59 QpackHuffmanDecoder huffmanDecoder) {
60 this.maxTableCapacity = checkInRange(
61 maxTableCapacity == null ? 0 : maxTableCapacity, 0, MAX_UNSIGNED_INT, "maxTableCapacity");
62 this.huffmanDecoder = huffmanDecoder;
63 this.qpackDecoder = qpackDecoder;
64 }
65
66 @Override
67 protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> __) throws Exception {
68 if (!in.isReadable()) {
69 return;
70 }
71 if (discard) {
72 in.skipBytes(in.readableBytes());
73 return;
74 }
75
76 if (pendingName != null) {
77
78
79 decodeInsertLiteralValue(ctx, in, Http3.getQpackAttributes(ctx.channel().parent()));
80 return;
81 }
82
83 byte b = in.getByte(in.readerIndex());
84
85
86
87
88
89
90
91 if ((b & 0b1110_0000) == 0b0010_0000) {
92
93 try {
94 long capacity = QpackUtil.decodePrefixedInteger(in, 5);
95 if (capacity < 0) {
96
97 return;
98 }
99
100 qpackDecoder.setDynamicTableCapacity(capacity);
101 } catch (QpackException e) {
102 handleDecodeFailure(ctx, e, "setDynamicTableCapacity failed.");
103 }
104 return;
105 }
106
107 final QpackAttributes qpackAttributes = Http3.getQpackAttributes(ctx.channel().parent());
108 assert qpackAttributes != null;
109 if (!qpackAttributes.dynamicTableDisabled() && !qpackAttributes.decoderStreamAvailable()) {
110
111 return;
112 }
113 final QuicStreamChannel decoderStream = qpackAttributes.decoderStream();
114
115
116
117
118
119
120
121
122
123
124
125 if ((b & 0b1000_0000) == 0b1000_0000) {
126 int readerIndex = in.readerIndex();
127
128
129 final boolean isStaticTableIndex = QpackUtil.firstByteEquals(in, (byte) 0b1100_0000);
130 final int nameIdx = decodePrefixedIntegerAsInt(in, 6);
131 if (nameIdx < 0) {
132
133 return;
134 }
135
136 try {
137 CharSequence value = decodeLiteralValue(in);
138 if (value == null) {
139
140 in.readerIndex(readerIndex);
141
142 return;
143 }
144 qpackDecoder.insertWithNameReference(decoderStream, isStaticTableIndex, nameIdx,
145 value);
146 } catch (QpackException e) {
147 handleDecodeFailure(ctx, e, "insertWithNameReference failed.");
148 }
149 return;
150 }
151
152
153
154
155
156
157
158
159
160
161
162
163 if ((b & 0b1100_0000) == 0b0100_0000) {
164 int readerIndex = in.readerIndex();
165 final boolean nameHuffEncoded = QpackUtil.firstByteEquals(in, (byte) 0b0110_0000);
166 int nameLength = decodePrefixedIntegerAsInt(in, 5);
167 if (nameLength < 0) {
168
169 in.readerIndex(readerIndex);
170
171 return;
172 }
173
174 try {
175 checkStringLiteralLength(nameHuffEncoded, nameLength);
176 if (in.readableBytes() < nameLength) {
177
178 in.readerIndex(readerIndex);
179
180 return;
181 }
182
183
184
185 pendingName = decodeStringLiteral(in, nameHuffEncoded, nameLength);
186 } catch (QpackException e) {
187 handleDecodeFailure(ctx, e, "insertLiteral failed.");
188 return;
189 }
190 decodeInsertLiteralValue(ctx, in, qpackAttributes);
191 return;
192 }
193
194
195
196
197
198
199 if ((b & 0b1110_0000) == 0b0000_0000) {
200 int readerIndex = in.readerIndex();
201 int index = decodePrefixedIntegerAsInt(in, 5);
202 if (index < 0) {
203
204 in.readerIndex(readerIndex);
205
206 return;
207 }
208 try {
209 qpackDecoder.duplicate(decoderStream, index);
210 } catch (QpackException e) {
211 handleDecodeFailure(ctx, e, "duplicate failed.");
212 }
213 return;
214 }
215
216 discard = true;
217 Http3CodecUtils.connectionError(ctx, Http3ErrorCode.QPACK_ENCODER_STREAM_ERROR,
218 "Unknown encoder instruction '" + b + "'.", false);
219 }
220
221 @Override
222 public void channelReadComplete(ChannelHandlerContext ctx) {
223 ctx.fireChannelReadComplete();
224
225
226
227 Http3CodecUtils.readIfNoAutoRead(ctx);
228 }
229
230 @Override
231 public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
232 if (evt instanceof ChannelInputShutdownEvent) {
233
234 Http3CodecUtils.criticalStreamClosed(ctx);
235 }
236 ctx.fireUserEventTriggered(evt);
237 }
238
239 @Override
240 public void channelInactive(ChannelHandlerContext ctx) {
241
242 Http3CodecUtils.criticalStreamClosed(ctx);
243 ctx.fireChannelInactive();
244 }
245
246 private void decodeInsertLiteralValue(ChannelHandlerContext ctx, ByteBuf in, QpackAttributes qpackAttributes) {
247 assert pendingName != null;
248 try {
249 CharSequence value = decodeLiteralValue(in);
250 if (value == null) {
251
252 return;
253 }
254 CharSequence name = pendingName;
255 pendingName = null;
256 qpackDecoder.insertLiteral(qpackAttributes.decoderStream(), name, value);
257 } catch (QpackException e) {
258 handleDecodeFailure(ctx, e, "insertLiteral failed.");
259 }
260 }
261
262 private void handleDecodeFailure(ChannelHandlerContext ctx, QpackException cause, String message) {
263 discard = true;
264 pendingName = null;
265 connectionError(ctx, new Http3Exception(QPACK_ENCODER_STREAM_ERROR, message, cause), true);
266 }
267
268 @Nullable
269 private CharSequence decodeLiteralValue(ByteBuf in) throws QpackException {
270 int readerIndex = in.readerIndex();
271 int valueLength = decodePrefixedIntegerAsInt(in, 7);
272 if (valueLength < 0) {
273
274 return null;
275 }
276 final boolean valueHuffEncoded = QpackUtil.byteEquals(in, readerIndex, (byte) 0b1000_0000);
277 checkStringLiteralLength(valueHuffEncoded, valueLength);
278 if (in.readableBytes() < valueLength) {
279
280 in.readerIndex(readerIndex);
281 return null;
282 }
283 return decodeStringLiteral(in, valueHuffEncoded, valueLength);
284 }
285
286 private void checkStringLiteralLength(boolean huffmanEncoded, int length) throws QpackException {
287
288
289
290
291
292
293
294
295 final long limit = huffmanEncoded ? (maxTableCapacity * 8 + 4) / 5 : maxTableCapacity;
296 if (length > limit) {
297 throw STRING_LITERAL_TOO_LARGE;
298 }
299 }
300
301 private CharSequence decodeStringLiteral(ByteBuf in, boolean huffmanEncoded, int length)
302 throws QpackException {
303 if (huffmanEncoded) {
304 return huffmanDecoder.decode(in, length);
305 }
306 if (in.readableBytes() < length) {
307 throw INVALID_LENGTH_STRING_LITERAL;
308 }
309 byte[] buf = new byte[length];
310 in.readBytes(buf);
311 return new AsciiString(buf, false);
312 }
313 }