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