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