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