View Javadoc
1   /*
2    * Copyright 2014 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
5    * "License"); you may not use this file except in compliance with the License. You may obtain a
6    * copy of the License at:
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software distributed under the License
11   * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12   * or implied. See the License for the specific language governing permissions and limitations under
13   * the License.
14   */
15  
16  package io.netty.handler.codec.http2;
17  
18  import io.netty.buffer.ByteBuf;
19  import io.netty.buffer.ByteBufUtil;
20  import io.netty.buffer.Unpooled;
21  import io.netty.channel.Channel;
22  import io.netty.channel.ChannelHandlerContext;
23  import io.netty.channel.ChannelPromise;
24  import io.netty.channel.DefaultChannelPromise;
25  import io.netty.handler.ssl.ApplicationProtocolNames;
26  import io.netty.util.AsciiString;
27  import io.netty.util.concurrent.EventExecutor;
28  import io.netty.util.internal.UnstableApi;
29  
30  import static io.netty.buffer.Unpooled.directBuffer;
31  import static io.netty.buffer.Unpooled.unreleasableBuffer;
32  import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
33  import static io.netty.handler.codec.http2.Http2Exception.connectionError;
34  import static io.netty.handler.codec.http2.Http2Exception.headerListSizeError;
35  import static io.netty.util.CharsetUtil.UTF_8;
36  import static java.lang.Math.max;
37  import static java.lang.Math.min;
38  import static java.util.concurrent.TimeUnit.MILLISECONDS;
39  import static java.util.concurrent.TimeUnit.SECONDS;
40  
41  /**
42   * Constants and utility method used for encoding/decoding HTTP2 frames.
43   */
44  @UnstableApi
45  public final class Http2CodecUtil {
46      public static final int CONNECTION_STREAM_ID = 0;
47      public static final int HTTP_UPGRADE_STREAM_ID = 1;
48      public static final CharSequence HTTP_UPGRADE_SETTINGS_HEADER = AsciiString.cached("HTTP2-Settings");
49      public static final CharSequence HTTP_UPGRADE_PROTOCOL_NAME = "h2c";
50      public static final CharSequence TLS_UPGRADE_PROTOCOL_NAME = ApplicationProtocolNames.HTTP_2;
51  
52      public static final int PING_FRAME_PAYLOAD_LENGTH = 8;
53      public static final short MAX_UNSIGNED_BYTE = 0xff;
54      /**
55       * The maximum number of padding bytes. That is the 255 padding bytes appended to the end of a frame and the 1 byte
56       * pad length field.
57       */
58      public static final int MAX_PADDING = 256;
59      public static final long MAX_UNSIGNED_INT = 0xffffffffL;
60      public static final int FRAME_HEADER_LENGTH = 9;
61      public static final int SETTING_ENTRY_LENGTH = 6;
62      public static final int PRIORITY_ENTRY_LENGTH = 5;
63      public static final int INT_FIELD_LENGTH = 4;
64      public static final short MAX_WEIGHT = 256;
65      public static final short MIN_WEIGHT = 1;
66  
67      private static final ByteBuf CONNECTION_PREFACE =
68              unreleasableBuffer(directBuffer(24).writeBytes("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".getBytes(UTF_8)))
69                      .asReadOnly();
70      private static final ByteBuf EMPTY_PING =
71              unreleasableBuffer(directBuffer(PING_FRAME_PAYLOAD_LENGTH).writeZero(PING_FRAME_PAYLOAD_LENGTH))
72                      .asReadOnly();
73  
74      private static final int MAX_PADDING_LENGTH_LENGTH = 1;
75      public static final int DATA_FRAME_HEADER_LENGTH = FRAME_HEADER_LENGTH + MAX_PADDING_LENGTH_LENGTH;
76      public static final int HEADERS_FRAME_HEADER_LENGTH =
77              FRAME_HEADER_LENGTH + MAX_PADDING_LENGTH_LENGTH + INT_FIELD_LENGTH + 1;
78      public static final int PRIORITY_FRAME_LENGTH = FRAME_HEADER_LENGTH + PRIORITY_ENTRY_LENGTH;
79      public static final int RST_STREAM_FRAME_LENGTH = FRAME_HEADER_LENGTH + INT_FIELD_LENGTH;
80      public static final int PUSH_PROMISE_FRAME_HEADER_LENGTH =
81              FRAME_HEADER_LENGTH + MAX_PADDING_LENGTH_LENGTH + INT_FIELD_LENGTH;
82      public static final int GO_AWAY_FRAME_HEADER_LENGTH = FRAME_HEADER_LENGTH + 2 * INT_FIELD_LENGTH;
83      public static final int WINDOW_UPDATE_FRAME_LENGTH = FRAME_HEADER_LENGTH + INT_FIELD_LENGTH;
84      public static final int CONTINUATION_FRAME_HEADER_LENGTH = FRAME_HEADER_LENGTH + MAX_PADDING_LENGTH_LENGTH;
85  
86      public static final char SETTINGS_HEADER_TABLE_SIZE = 1;
87      public static final char SETTINGS_ENABLE_PUSH = 2;
88      public static final char SETTINGS_MAX_CONCURRENT_STREAMS = 3;
89      public static final char SETTINGS_INITIAL_WINDOW_SIZE = 4;
90      public static final char SETTINGS_MAX_FRAME_SIZE = 5;
91      public static final char SETTINGS_MAX_HEADER_LIST_SIZE = 6;
92      public static final int NUM_STANDARD_SETTINGS = 6;
93  
94      public static final long MAX_HEADER_TABLE_SIZE = MAX_UNSIGNED_INT;
95      public static final long MAX_CONCURRENT_STREAMS = MAX_UNSIGNED_INT;
96      public static final int MAX_INITIAL_WINDOW_SIZE = Integer.MAX_VALUE;
97      public static final int MAX_FRAME_SIZE_LOWER_BOUND = 0x4000;
98      public static final int MAX_FRAME_SIZE_UPPER_BOUND = 0xffffff;
99      public static final long MAX_HEADER_LIST_SIZE = MAX_UNSIGNED_INT;
100 
101     public static final long MIN_HEADER_TABLE_SIZE = 0;
102     public static final long MIN_CONCURRENT_STREAMS = 0;
103     public static final int MIN_INITIAL_WINDOW_SIZE = 0;
104     public static final long MIN_HEADER_LIST_SIZE = 0;
105 
106     public static final int DEFAULT_WINDOW_SIZE = 65535;
107     public static final short DEFAULT_PRIORITY_WEIGHT = 16;
108     public static final int DEFAULT_HEADER_TABLE_SIZE = 4096;
109     /**
110      * <a href="https://tools.ietf.org/html/rfc7540#section-6.5.2">The initial value of this setting is unlimited</a>.
111      * However in practice we don't want to allow our peers to use unlimited memory by default. So we take advantage
112      * of the <q>For any given request, a lower limit than what is advertised MAY be enforced.</q> loophole.
113      */
114     public static final long DEFAULT_HEADER_LIST_SIZE = 8192;
115     public static final int DEFAULT_MAX_FRAME_SIZE = MAX_FRAME_SIZE_LOWER_BOUND;
116     /**
117      * The assumed minimum value for {@code SETTINGS_MAX_CONCURRENT_STREAMS} as
118      * recommended by the <a herf="https://tools.ietf.org/html/rfc7540#section-6.5.2">HTTP/2 spec</a>.
119      */
120     public static final int SMALLEST_MAX_CONCURRENT_STREAMS = 100;
121     static final int DEFAULT_MAX_RESERVED_STREAMS = SMALLEST_MAX_CONCURRENT_STREAMS;
122     static final int DEFAULT_MIN_ALLOCATION_CHUNK = 1024;
123     static final int DEFAULT_INITIAL_HUFFMAN_DECODE_CAPACITY = 32;
124 
125     /**
126      * Calculate the threshold in bytes which should trigger a {@code GO_AWAY} if a set of headers exceeds this amount.
127      * @param maxHeaderListSize
128      *      <a href="https://tools.ietf.org/html/rfc7540#section-6.5.2">SETTINGS_MAX_HEADER_LIST_SIZE</a> for the local
129      *      endpoint.
130      * @return the threshold in bytes which should trigger a {@code GO_AWAY} if a set of headers exceeds this amount.
131      */
132     public static long calculateMaxHeaderListSizeGoAway(long maxHeaderListSize) {
133         // This is equivalent to `maxHeaderListSize * 1.25` but we avoid floating point multiplication.
134         return maxHeaderListSize + (maxHeaderListSize >>> 2);
135     }
136 
137     public static final long DEFAULT_GRACEFUL_SHUTDOWN_TIMEOUT_MILLIS = MILLISECONDS.convert(30, SECONDS);
138 
139     /**
140      * Returns {@code true} if the stream is an outbound stream.
141      *
142      * @param server    {@code true} if the endpoint is a server, {@code false} otherwise.
143      * @param streamId  the stream identifier
144      */
145     public static boolean isOutboundStream(boolean server, int streamId) {
146         boolean even = (streamId & 1) == 0;
147         return streamId > 0 && server == even;
148     }
149 
150     /**
151      * Returns true if the {@code streamId} is a valid HTTP/2 stream identifier.
152      */
153     public static boolean isStreamIdValid(int streamId) {
154         return streamId >= 0;
155     }
156 
157     /**
158      * Indicates whether or not the given value for max frame size falls within the valid range.
159      */
160     public static boolean isMaxFrameSizeValid(int maxFrameSize) {
161         return maxFrameSize >= MAX_FRAME_SIZE_LOWER_BOUND && maxFrameSize <= MAX_FRAME_SIZE_UPPER_BOUND;
162     }
163 
164     /**
165      * Returns a buffer containing the {@link #CONNECTION_PREFACE}.
166      */
167     public static ByteBuf connectionPrefaceBuf() {
168         // Return a duplicate so that modifications to the reader index will not affect the original buffer.
169         return CONNECTION_PREFACE.retainedDuplicate();
170     }
171 
172     /**
173      * Returns a buffer filled with all zeros that is the appropriate length for a PING frame.
174      */
175     public static ByteBuf emptyPingBuf() {
176         // Return a duplicate so that modifications to the reader index will not affect the original buffer.
177         return EMPTY_PING.retainedDuplicate();
178     }
179 
180     /**
181      * Iteratively looks through the causality chain for the given exception and returns the first
182      * {@link Http2Exception} or {@code null} if none.
183      */
184     public static Http2Exception getEmbeddedHttp2Exception(Throwable cause) {
185         while (cause != null) {
186             if (cause instanceof Http2Exception) {
187                 return (Http2Exception) cause;
188             }
189             cause = cause.getCause();
190         }
191         return null;
192     }
193 
194     /**
195      * Creates a buffer containing the error message from the given exception. If the cause is
196      * {@code null} returns an empty buffer.
197      */
198     public static ByteBuf toByteBuf(ChannelHandlerContext ctx, Throwable cause) {
199         if (cause == null || cause.getMessage() == null) {
200             return Unpooled.EMPTY_BUFFER;
201         }
202 
203         return ByteBufUtil.writeUtf8(ctx.alloc(), cause.getMessage());
204     }
205 
206     /**
207      * Reads a big-endian (31-bit) integer from the buffer.
208      */
209     public static int readUnsignedInt(ByteBuf buf) {
210         return buf.readInt() & 0x7fffffff;
211     }
212 
213     /**
214      * Writes an HTTP/2 frame header to the output buffer.
215      */
216     public static void writeFrameHeader(ByteBuf out, int payloadLength, byte type,
217             Http2Flags flags, int streamId) {
218         out.ensureWritable(FRAME_HEADER_LENGTH + payloadLength);
219         writeFrameHeaderInternal(out, payloadLength, type, flags, streamId);
220     }
221 
222     /**
223      * Calculate the amount of bytes that can be sent by {@code state}. The lower bound is {@code 0}.
224      */
225     public static int streamableBytes(StreamByteDistributor.StreamState state) {
226         return max(0, (int) min(state.pendingBytes(), state.windowSize()));
227     }
228 
229     /**
230      * Results in a RST_STREAM being sent for {@code streamId} due to violating
231      * <a href="https://tools.ietf.org/html/rfc7540#section-6.5.2">SETTINGS_MAX_HEADER_LIST_SIZE</a>.
232      * @param streamId The stream ID that was being processed when the exceptional condition occurred.
233      * @param maxHeaderListSize The max allowed size for a list of headers in bytes which was exceeded.
234      * @param onDecode {@code true} if the exception was encountered during decoder. {@code false} for encode.
235      * @throws Http2Exception a stream error.
236      */
237     public static void headerListSizeExceeded(int streamId, long maxHeaderListSize,
238                                               boolean onDecode) throws Http2Exception {
239         throw headerListSizeError(streamId, PROTOCOL_ERROR, onDecode, "Header size exceeded max " +
240                                   "allowed size (%d)", maxHeaderListSize);
241     }
242 
243     /**
244      * Results in a GO_AWAY being sent due to violating
245      * <a href="https://tools.ietf.org/html/rfc7540#section-6.5.2">SETTINGS_MAX_HEADER_LIST_SIZE</a> in an unrecoverable
246      * manner.
247      * @param maxHeaderListSize The max allowed size for a list of headers in bytes which was exceeded.
248      * @throws Http2Exception a connection error.
249      */
250     public static void headerListSizeExceeded(long maxHeaderListSize) throws Http2Exception {
251         throw connectionError(PROTOCOL_ERROR, "Header size exceeded max " +
252                 "allowed size (%d)", maxHeaderListSize);
253     }
254 
255     static void writeFrameHeaderInternal(ByteBuf out, int payloadLength, byte type,
256             Http2Flags flags, int streamId) {
257         out.writeMedium(payloadLength);
258         out.writeByte(type);
259         out.writeByte(flags.value());
260         out.writeInt(streamId);
261     }
262 
263     /**
264      * Provides the ability to associate the outcome of multiple {@link ChannelPromise}
265      * objects into a single {@link ChannelPromise} object.
266      */
267     static final class SimpleChannelPromiseAggregator extends DefaultChannelPromise {
268         private final ChannelPromise promise;
269         private int expectedCount;
270         private int doneCount;
271         private Throwable lastFailure;
272         private boolean doneAllocating;
273 
274         SimpleChannelPromiseAggregator(ChannelPromise promise, Channel c, EventExecutor e) {
275             super(c, e);
276             assert promise != null && !promise.isDone();
277             this.promise = promise;
278         }
279 
280         /**
281          * Allocate a new promise which will be used to aggregate the overall success of this promise aggregator.
282          * @return A new promise which will be aggregated.
283          * {@code null} if {@link #doneAllocatingPromises()} was previously called.
284          */
285         public ChannelPromise newPromise() {
286             assert !doneAllocating : "Done allocating. No more promises can be allocated.";
287             ++expectedCount;
288             return this;
289         }
290 
291         /**
292          * Signify that no more {@link #newPromise()} allocations will be made.
293          * The aggregation can not be successful until this method is called.
294          * @return The promise that is the aggregation of all promises allocated with {@link #newPromise()}.
295          */
296         public ChannelPromise doneAllocatingPromises() {
297             if (!doneAllocating) {
298                 doneAllocating = true;
299                 if (doneCount == expectedCount || expectedCount == 0) {
300                     return setPromise();
301                 }
302             }
303             return this;
304         }
305 
306         @Override
307         public boolean tryFailure(Throwable cause) {
308             if (allowFailure()) {
309                 ++doneCount;
310                 lastFailure = cause;
311                 if (allPromisesDone()) {
312                     return tryPromise();
313                 }
314                 // TODO: We break the interface a bit here.
315                 // Multiple failure events can be processed without issue because this is an aggregation.
316                 return true;
317             }
318             return false;
319         }
320 
321         /**
322          * Fail this object if it has not already been failed.
323          * <p>
324          * This method will NOT throw an {@link IllegalStateException} if called multiple times
325          * because that may be expected.
326          */
327         @Override
328         public ChannelPromise setFailure(Throwable cause) {
329             if (allowFailure()) {
330                 ++doneCount;
331                 lastFailure = cause;
332                 if (allPromisesDone()) {
333                     return setPromise();
334                 }
335             }
336             return this;
337         }
338 
339         @Override
340         public ChannelPromise setSuccess(Void result) {
341             if (awaitingPromises()) {
342                 ++doneCount;
343                 if (allPromisesDone()) {
344                     setPromise();
345                 }
346             }
347             return this;
348         }
349 
350         @Override
351         public boolean trySuccess(Void result) {
352             if (awaitingPromises()) {
353                 ++doneCount;
354                 if (allPromisesDone()) {
355                     return tryPromise();
356                 }
357                 // TODO: We break the interface a bit here.
358                 // Multiple success events can be processed without issue because this is an aggregation.
359                 return true;
360             }
361             return false;
362         }
363 
364         private boolean allowFailure() {
365             return awaitingPromises() || expectedCount == 0;
366         }
367 
368         private boolean awaitingPromises() {
369             return doneCount < expectedCount;
370         }
371 
372         private boolean allPromisesDone() {
373             return doneCount == expectedCount && doneAllocating;
374         }
375 
376         private ChannelPromise setPromise() {
377             if (lastFailure == null) {
378                 promise.setSuccess();
379                 return super.setSuccess(null);
380             } else {
381                 promise.setFailure(lastFailure);
382                 return super.setFailure(lastFailure);
383             }
384         }
385 
386         private boolean tryPromise() {
387             if (lastFailure == null) {
388                 promise.trySuccess();
389                 return super.trySuccess(null);
390             } else {
391                 promise.tryFailure(lastFailure);
392                 return super.tryFailure(lastFailure);
393             }
394         }
395     }
396 
397     public static void verifyPadding(int padding) {
398         if (padding < 0 || padding > MAX_PADDING) {
399             throw new IllegalArgumentException(String.format("Invalid padding '%d'. Padding must be between 0 and " +
400                                                              "%d (inclusive).", padding, MAX_PADDING));
401         }
402     }
403     private Http2CodecUtil() { }
404 }