View Javadoc
1   /*
2    * Copyright 2020 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.buffer.Unpooled;
20  import io.netty.channel.Channel;
21  import io.netty.channel.ChannelFuture;
22  import io.netty.channel.ChannelHandlerContext;
23  import io.netty.handler.codec.quic.QuicChannel;
24  import io.netty.handler.codec.quic.QuicStreamChannel;
25  import io.netty.handler.codec.quic.QuicStreamType;
26  import io.netty.util.CharsetUtil;
27  import io.netty.util.internal.ObjectUtil;
28  import io.netty.util.internal.StringUtil;
29  import org.jetbrains.annotations.Nullable;
30  
31  import static io.netty.channel.ChannelFutureListener.CLOSE_ON_FAILURE;
32  import static io.netty.handler.codec.http3.Http3ErrorCode.H3_INTERNAL_ERROR;
33  import static io.netty.handler.codec.quic.QuicStreamType.UNIDIRECTIONAL;
34  
35  final class Http3CodecUtils {
36  
37      // See https://tools.ietf.org/html/draft-ietf-quic-http-32#section-7.2.8
38      static final long MIN_RESERVED_FRAME_TYPE = 0x1f * 1 + 0x21;
39      static final long MAX_RESERVED_FRAME_TYPE = 0x1f * (long) Integer.MAX_VALUE + 0x21;
40  
41      // See https://tools.ietf.org/html/draft-ietf-quic-http-32#section-7.2
42      static final int HTTP3_DATA_FRAME_TYPE = 0x0;
43      static final int HTTP3_HEADERS_FRAME_TYPE = 0x1;
44      static final int HTTP3_CANCEL_PUSH_FRAME_TYPE = 0x3;
45      static final int HTTP3_SETTINGS_FRAME_TYPE = 0x4;
46      static final int HTTP3_PUSH_PROMISE_FRAME_TYPE = 0x5;
47      static final int HTTP3_GO_AWAY_FRAME_TYPE = 0x7;
48      static final int HTTP3_MAX_PUSH_ID_FRAME_TYPE = 0xd;
49  
50      static final int HTTP3_CANCEL_PUSH_FRAME_MAX_LEN = 8;
51      static final int HTTP3_SETTINGS_FRAME_MAX_LEN = 256;
52      static final int HTTP3_GO_AWAY_FRAME_MAX_LEN = 8;
53      static final int HTTP3_MAX_PUSH_ID_FRAME_MAX_LEN = 8;
54  
55      static final int HTTP3_CONTROL_STREAM_TYPE = 0x00;
56      static final int HTTP3_PUSH_STREAM_TYPE = 0x01;
57      static final int HTTP3_QPACK_ENCODER_STREAM_TYPE = 0x02;
58      static final int HTTP3_QPACK_DECODER_STREAM_TYPE = 0x03;
59  
60      /**
61       * Default is unlimited in the RFC but we want to enforce some "security" default limit.
62       * See <a href="https://datatracker.ietf.org/doc/html/rfc9114#section-4.2.2">RFC9114 Section 4.2.2</a>.
63       */
64      static final long DEFAULT_MAX_FIELD_SECTION_SIZE = 8192;
65  
66      // Let's use 32kb as max
67      static final int DEFAULT_MAX_UNKNOWN_FRAME_PAYLOAD_LENGTH = 32 * 1024;
68  
69      private Http3CodecUtils() { }
70  
71      static long checkIsReservedFrameType(long type) {
72          return ObjectUtil.checkInRange(type, MIN_RESERVED_FRAME_TYPE, MAX_RESERVED_FRAME_TYPE, "type");
73      }
74  
75      static boolean isReservedFrameType(long type) {
76          return type >= MIN_RESERVED_FRAME_TYPE && type <= MAX_RESERVED_FRAME_TYPE;
77      }
78  
79      /**
80       * Checks if the passed {@link QuicStreamChannel} is a server initiated stream.
81       *
82       * @param channel to check.
83       * @return {@code true} if the passed {@link QuicStreamChannel} is a server initiated stream.
84       */
85      static boolean isServerInitiatedQuicStream(QuicStreamChannel channel) {
86          // Server streams have odd stream id
87          // https://www.rfc-editor.org/rfc/rfc9000.html#name-stream-types-and-identifier
88          return channel.streamId() % 2 != 0;
89      }
90  
91      static boolean isReservedHttp2FrameType(long type) {
92          switch ((int) type) {
93              // Reserved types that were used in HTTP/2
94              // https://tools.ietf.org/html/draft-ietf-quic-http-32#section-11.2.1
95              case 0x2:
96              case 0x6:
97              case 0x8:
98              case 0x9:
99                  return true;
100             default:
101                 return false;
102         }
103     }
104 
105     static boolean isReservedHttp2Setting(long key) {
106         // Reserved types that were used in HTTP/2
107         // https://tools.ietf.org/html/draft-ietf-quic-http-32#section-11.2.2
108         return 0x2L <= key && key <= 0x5L;
109     }
110 
111     /**
112      * Returns the number of bytes needed to encode the variable length integer.
113      *
114      * See <a href="https://tools.ietf.org/html/draft-ietf-quic-transport-32#section-16">
115      *     Variable-Length Integer Encoding</a>.
116      */
117     static int numBytesForVariableLengthInteger(long value) {
118         if (value <= 63) {
119             return 1;
120         }
121         if (value <= 16383) {
122             return 2;
123         }
124         if (value <= 1073741823) {
125             return 4;
126         }
127         if (value <= 4611686018427387903L) {
128             return 8;
129         }
130         throw new IllegalArgumentException();
131     }
132 
133     /**
134      * Write the variable length integer into the {@link ByteBuf}.
135      *
136      * See <a href="https://tools.ietf.org/html/draft-ietf-quic-transport-32#section-16">
137      *     Variable-Length Integer Encoding</a>.
138      */
139     static void writeVariableLengthInteger(ByteBuf out, long value) {
140         int numBytes = numBytesForVariableLengthInteger(value);
141         writeVariableLengthInteger(out, value, numBytes);
142     }
143 
144     /**
145      * Write the variable length integer into the {@link ByteBuf}.
146      *
147      * See <a href="https://tools.ietf.org/html/draft-ietf-quic-transport-32#section-16">
148      *     Variable-Length Integer Encoding</a>.
149      */
150     static void writeVariableLengthInteger(ByteBuf out, long value, int numBytes) {
151         int writerIndex = out.writerIndex();
152         switch (numBytes) {
153             case 1:
154                 out.writeByte((byte) value);
155                 break;
156             case 2:
157                 out.writeShort((short) value);
158                 encodeLengthIntoBuffer(out, writerIndex, (byte) 0x40);
159                 break;
160             case 4:
161                 out.writeInt((int) value);
162                 encodeLengthIntoBuffer(out, writerIndex, (byte) 0x80);
163                 break;
164             case 8:
165                 out.writeLong(value);
166                 encodeLengthIntoBuffer(out, writerIndex, (byte) 0xc0);
167                 break;
168             default:
169                 throw new IllegalArgumentException();
170         }
171     }
172 
173     private static void encodeLengthIntoBuffer(ByteBuf out, int index, byte b) {
174         out.setByte(index, out.getByte(index) | b);
175     }
176 
177     /**
178      * Read the variable length integer from the {@link ByteBuf}.
179      *
180      * See <a href="https://tools.ietf.org/html/draft-ietf-quic-transport-32#section-16">
181      *     Variable-Length Integer Encoding </a>
182      */
183     static long readVariableLengthInteger(ByteBuf in, int len) {
184         switch (len) {
185             case 1:
186                 return in.readUnsignedByte();
187             case 2:
188                 return in.readUnsignedShort() & 0x3fff;
189             case 4:
190                 return in.readUnsignedInt() & 0x3fffffff;
191             case 8:
192                 return in.readLong() & 0x3fffffffffffffffL;
193             default:
194                 throw new IllegalArgumentException();
195         }
196     }
197 
198     /**
199      * Returns the number of bytes that were encoded into the byte for a variable length integer to read.
200      *
201      * See <a href="https://tools.ietf.org/html/draft-ietf-quic-transport-32#section-16">
202      *     Variable-Length Integer Encoding </a>
203      */
204     static int numBytesForVariableLengthInteger(byte b) {
205         byte val = (byte) (b >> 6);
206         if ((val & 1) != 0) {
207             if ((val & 2) != 0) {
208                 return 8;
209             }
210             return 2;
211         }
212         if ((val & 2) != 0) {
213             return 4;
214         }
215         return 1;
216     }
217 
218     static void criticalStreamClosed(ChannelHandlerContext ctx) {
219         if (ctx.channel().parent().isActive()) {
220             // Stream was closed while the parent channel is still active
221             Http3CodecUtils.connectionError(
222                     ctx, Http3ErrorCode.H3_CLOSED_CRITICAL_STREAM, "Critical stream closed.", false);
223         }
224     }
225 
226     /**
227      * A connection-error should be handled as defined in the HTTP3 spec.
228      * @param ctx           the {@link ChannelHandlerContext} of the handle that handles it.
229      * @param exception     the {@link Http3Exception} that caused the error.
230      * @param fireException {@code true} if we should also fire the {@link Http3Exception} through the pipeline.
231      */
232     static void connectionError(ChannelHandlerContext ctx, Http3Exception exception, boolean fireException) {
233         if (fireException) {
234             ctx.fireExceptionCaught(exception);
235         }
236         connectionError(ctx.channel(), exception.errorCode(), exception.getMessage());
237     }
238 
239     /**
240      * A connection-error should be handled as defined in the HTTP3 spec.
241      *
242      * @param ctx           the {@link ChannelHandlerContext} of the handle that handles it.
243      * @param errorCode     the {@link Http3ErrorCode} that caused the error.
244      * @param msg           the message that should be used as reason for the error, may be {@code null}.
245      * @param fireException {@code true} if we should also fire the {@link Http3Exception} through the pipeline.
246      */
247     static void connectionError(ChannelHandlerContext ctx, Http3ErrorCode errorCode,
248                                 @Nullable String msg, boolean fireException) {
249          if (fireException) {
250              ctx.fireExceptionCaught(new Http3Exception(errorCode, msg));
251          }
252          connectionError(ctx.channel(), errorCode, msg);
253     }
254 
255     /**
256      * Closes the channel if the passed {@link ChannelFuture} fails or has already failed.
257      *
258      * @param future {@link ChannelFuture} which if fails will close the channel.
259      */
260     static void closeOnFailure(ChannelFuture future) {
261         if (future.isDone() && !future.isSuccess()) {
262             future.channel().close();
263             return;
264         }
265         future.addListener(CLOSE_ON_FAILURE);
266     }
267 
268     /**
269      * A connection-error should be handled as defined in the HTTP3 spec.
270      *
271      * @param channel       the {@link Channel} on which error has occurred.
272      * @param errorCode     the {@link Http3ErrorCode} that caused the error.
273      * @param msg           the message that should be used as reason for the error, may be {@code null}.
274      */
275     static void connectionError(Channel channel, Http3ErrorCode errorCode, @Nullable String msg) {
276         final QuicChannel quicChannel;
277 
278         if (channel instanceof QuicChannel) {
279             quicChannel = (QuicChannel) channel;
280         } else {
281             quicChannel = (QuicChannel) channel.parent();
282         }
283         final ByteBuf buffer;
284         if (msg != null) {
285             // As we call an operation on the parent we should also use the parents allocator to allocate the buffer.
286             buffer = quicChannel.alloc().buffer();
287             buffer.writeCharSequence(msg, CharsetUtil.US_ASCII);
288         } else {
289             buffer = Unpooled.EMPTY_BUFFER;
290         }
291         quicChannel.close(true, errorCode.code, buffer);
292     }
293 
294     static void streamError(ChannelHandlerContext ctx, Http3ErrorCode errorCode) {
295         ((QuicStreamChannel) ctx.channel()).shutdownOutput(errorCode.code);
296     }
297 
298     static void readIfNoAutoRead(ChannelHandlerContext ctx) {
299         if (!ctx.channel().config().isAutoRead()) {
300             ctx.read();
301         }
302     }
303 
304     /**
305      * Retrieves {@link Http3ConnectionHandler} from the passed {@link QuicChannel} pipeline or closes the connection if
306      * none available.
307      *
308      * @param ch for which the {@link Http3ConnectionHandler} is to be retrieved.
309      * @return {@link Http3ConnectionHandler} if available, else close the connection and return {@code null}.
310      */
311     @Nullable
312     static Http3ConnectionHandler getConnectionHandlerOrClose(QuicChannel ch) {
313         Http3ConnectionHandler connectionHandler = ch.pipeline().get(Http3ConnectionHandler.class);
314         if (connectionHandler == null) {
315             connectionError(ch, H3_INTERNAL_ERROR, "Couldn't obtain the " +
316                     StringUtil.simpleClassName(Http3ConnectionHandler.class) + " of the parent Channel");
317             return null;
318         }
319         return connectionHandler;
320     }
321 
322     /**
323      * Verify if the passed {@link QuicStreamChannel} is a {@link QuicStreamType#UNIDIRECTIONAL} QUIC stream.
324      *
325      * @param ch to verify
326      * @throws IllegalArgumentException if the passed {@link QuicStreamChannel} is not a
327      * {@link QuicStreamType#UNIDIRECTIONAL} QUIC stream.
328      */
329     static void verifyIsUnidirectional(QuicStreamChannel ch) {
330         if (ch.type() != UNIDIRECTIONAL) {
331             throw new IllegalArgumentException("Invalid stream type: " + ch.type() + " for stream: " + ch.streamId());
332         }
333     }
334 }