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