View Javadoc
1   /*
2    * Copyright 2021 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
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          // 4.3.1. Set Dynamic Table Capacity
59          //
60          //   0   1   2   3   4   5   6   7
61          //+---+---+---+---+---+---+---+---+
62          //| 0 | 0 | 1 |   Capacity (5+)   |
63          //+---+---+---+-------------------+
64          if ((b & 0b1110_0000) == 0b0010_0000) {
65              // new capacity
66              long capacity = QpackUtil.decodePrefixedInteger(in, 5);
67              if (capacity < 0) {
68                  // Not enough readable bytes
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              // We need the decoder stream to update the decoder with these instructions.
84              return;
85          }
86          final QuicStreamChannel decoderStream = qpackAttributes.decoderStream();
87  
88          // 4.3.2. Insert With Name Reference
89          //
90          //      0   1   2   3   4   5   6   7
91          //   +---+---+---+---+---+---+---+---+
92          //   | 1 | T |    Name Index (6+)    |
93          //   +---+---+-----------------------+
94          //   | H |     Value Length (7+)     |
95          //   +---+---------------------------+
96          //   |  Value String (Length bytes)  |
97          //   +-------------------------------+
98          if ((b & 0b1000_0000) == 0b1000_0000) {
99              int readerIndex = in.readerIndex();
100             // T == 1 implies static table index.
101             // https://www.rfc-editor.org/rfc/rfc9204.html#name-insert-with-name-reference
102             final boolean isStaticTableIndex = QpackUtil.firstByteEquals(in, (byte) 0b1100_0000);
103             final int nameIdx = decodePrefixedIntegerAsInt(in, 6);
104             if (nameIdx < 0) {
105                 // Not enough readable bytes
106                 return;
107             }
108 
109             CharSequence value = decodeLiteralValue(in);
110             if (value == null) {
111                 // Reset readerIndex
112                 in.readerIndex(readerIndex);
113                 // Not enough readable bytes
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         // 4.3.3. Insert With Literal Name
125         //
126         //      0   1   2   3   4   5   6   7
127         //   +---+---+---+---+---+---+---+---+
128         //   | 0 | 1 | H | Name Length (5+)  |
129         //   +---+---+---+-------------------+
130         //   |  Name String (Length bytes)   |
131         //   +---+---------------------------+
132         //   | H |     Value Length (7+)     |
133         //   +---+---------------------------+
134         //   |  Value String (Length bytes)  |
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                 // Reset readerIndex
142                 in.readerIndex(readerIndex);
143                 // Not enough readable bytes
144                 return;
145             }
146             if (in.readableBytes() < nameLength) {
147                 // Reset readerIndex
148                 in.readerIndex(readerIndex);
149                 // Not enough readable bytes
150                 return;
151             }
152 
153             CharSequence name = decodeStringLiteral(in, nameHuffEncoded, nameLength);
154             CharSequence value = decodeLiteralValue(in);
155             if (value == null) {
156                 // Reset readerIndex
157                 in.readerIndex(readerIndex);
158                 // Not enough readable bytes
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         // 4.3.4. Duplicate
169         //
170         //      0   1   2   3   4   5   6   7
171         //   +---+---+---+---+---+---+---+---+
172         //   | 0 | 0 | 0 |    Index (5+)     |
173         //   +---+---+---+-------------------+
174         if ((b & 0b1110_0000) == 0b0000_0000) {
175             int readerIndex = in.readerIndex();
176             int index = decodePrefixedIntegerAsInt(in, 5);
177             if (index < 0) {
178                 // Reset readerIndex
179                 in.readerIndex(readerIndex);
180                 // Not enough readable bytes
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         // QPACK streams should always be processed, no matter what the user is doing in terms of configuration
201         // and AUTO_READ.
202         Http3CodecUtils.readIfNoAutoRead(ctx);
203     }
204 
205     @Override
206     public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
207         if (evt instanceof ChannelInputShutdownEvent) {
208             // See https://www.rfc-editor.org/rfc/rfc9204.html#name-encoder-and-decoder-streams
209             Http3CodecUtils.criticalStreamClosed(ctx);
210         }
211         ctx.fireUserEventTriggered(evt);
212     }
213 
214     @Override
215     public void channelInactive(ChannelHandlerContext ctx) {
216         // See https://www.rfc-editor.org/rfc/rfc9204.html#name-encoder-and-decoder-streams
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             // Not enough readable bytes
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 }