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