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      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          // 4.3.1. Set Dynamic Table Capacity
61          //
62          //   0   1   2   3   4   5   6   7
63          //+---+---+---+---+---+---+---+---+
64          //| 0 | 0 | 1 |   Capacity (5+)   |
65          //+---+---+---+-------------------+
66          if ((b & 0b1110_0000) == 0b0010_0000) {
67              // new capacity
68              long capacity = QpackUtil.decodePrefixedInteger(in, 5);
69              if (capacity < 0) {
70                  // Not enough readable bytes
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              // We need the decoder stream to update the decoder with these instructions.
86              return;
87          }
88          final QuicStreamChannel decoderStream = qpackAttributes.decoderStream();
89  
90          // 4.3.2. Insert With Name Reference
91          //
92          //      0   1   2   3   4   5   6   7
93          //   +---+---+---+---+---+---+---+---+
94          //   | 1 | T |    Name Index (6+)    |
95          //   +---+---+-----------------------+
96          //   | H |     Value Length (7+)     |
97          //   +---+---------------------------+
98          //   |  Value String (Length bytes)  |
99          //   +-------------------------------+
100         if ((b & 0b1000_0000) == 0b1000_0000) {
101             int readerIndex = in.readerIndex();
102             // T == 1 implies static table index.
103             // https://www.rfc-editor.org/rfc/rfc9204.html#name-insert-with-name-reference
104             final boolean isStaticTableIndex = QpackUtil.firstByteEquals(in, (byte) 0b1100_0000);
105             final int nameIdx = decodePrefixedIntegerAsInt(in, 6);
106             if (nameIdx < 0) {
107                 // Not enough readable bytes
108                 return;
109             }
110 
111             CharSequence value = decodeLiteralValue(in);
112             if (value == null) {
113                 // Reset readerIndex
114                 in.readerIndex(readerIndex);
115                 // Not enough readable bytes
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         // 4.3.3. Insert With Literal Name
127         //
128         //      0   1   2   3   4   5   6   7
129         //   +---+---+---+---+---+---+---+---+
130         //   | 0 | 1 | H | Name Length (5+)  |
131         //   +---+---+---+-------------------+
132         //   |  Name String (Length bytes)   |
133         //   +---+---------------------------+
134         //   | H |     Value Length (7+)     |
135         //   +---+---------------------------+
136         //   |  Value String (Length bytes)  |
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                 // Reset readerIndex
144                 in.readerIndex(readerIndex);
145                 // Not enough readable bytes
146                 return;
147             }
148             if (in.readableBytes() < nameLength) {
149                 // Reset readerIndex
150                 in.readerIndex(readerIndex);
151                 // Not enough readable bytes
152                 return;
153             }
154 
155             CharSequence name = decodeStringLiteral(in, nameHuffEncoded, nameLength);
156             CharSequence value = decodeLiteralValue(in);
157             if (value == null) {
158                 // Reset readerIndex
159                 in.readerIndex(readerIndex);
160                 // Not enough readable bytes
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         // 4.3.4. Duplicate
171         //
172         //      0   1   2   3   4   5   6   7
173         //   +---+---+---+---+---+---+---+---+
174         //   | 0 | 0 | 0 |    Index (5+)     |
175         //   +---+---+---+-------------------+
176         if ((b & 0b1110_0000) == 0b0000_0000) {
177             int readerIndex = in.readerIndex();
178             int index = decodePrefixedIntegerAsInt(in, 5);
179             if (index < 0) {
180                 // Reset readerIndex
181                 in.readerIndex(readerIndex);
182                 // Not enough readable bytes
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         // QPACK streams should always be processed, no matter what the user is doing in terms of configuration
203         // and AUTO_READ.
204         Http3CodecUtils.readIfNoAutoRead(ctx);
205     }
206 
207     @Override
208     public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
209         if (evt instanceof ChannelInputShutdownEvent) {
210             // See https://www.rfc-editor.org/rfc/rfc9204.html#name-encoder-and-decoder-streams
211             Http3CodecUtils.criticalStreamClosed(ctx);
212         }
213         ctx.fireUserEventTriggered(evt);
214     }
215 
216     @Override
217     public void channelInactive(ChannelHandlerContext ctx) {
218         // See https://www.rfc-editor.org/rfc/rfc9204.html#name-encoder-and-decoder-streams
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             // Not enough readable bytes
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 }