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