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 static io.netty.util.CharsetUtil.UTF_8;
19  import static io.netty.util.internal.ObjectUtil.checkNotNull;
20  import io.netty.buffer.ByteBuf;
21  import io.netty.buffer.Unpooled;
22  import io.netty.channel.Channel;
23  import io.netty.channel.ChannelHandlerContext;
24  import io.netty.channel.ChannelPromise;
25  import io.netty.channel.DefaultChannelPromise;
26  import io.netty.handler.codec.http2.Http2StreamRemovalPolicy.Action;
27  import io.netty.util.concurrent.EventExecutor;
28  
29  /**
30   * Constants and utility method used for encoding/decoding HTTP2 frames.
31   */
32  public final class Http2CodecUtil {
33  
34      private static final byte[] CONNECTION_PREFACE = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".getBytes(UTF_8);
35      private static final byte[] EMPTY_PING = new byte[8];
36  
37      public static final int CONNECTION_STREAM_ID = 0;
38      public static final int HTTP_UPGRADE_STREAM_ID = 1;
39      public static final String HTTP_UPGRADE_SETTINGS_HEADER = "HTTP2-Settings";
40      public static final String HTTP_UPGRADE_PROTOCOL_NAME = "h2c-16";
41      public static final String TLS_UPGRADE_PROTOCOL_NAME = "h2-16";
42  
43      public static final int PING_FRAME_PAYLOAD_LENGTH = 8;
44      public static final short MAX_UNSIGNED_BYTE = 0xFF;
45      public static final int MAX_UNSIGNED_SHORT = 0xFFFF;
46      public static final long MAX_UNSIGNED_INT = 0xFFFFFFFFL;
47      public static final int FRAME_HEADER_LENGTH = 9;
48      public static final int SETTING_ENTRY_LENGTH = 6;
49      public static final int PRIORITY_ENTRY_LENGTH = 5;
50      public static final int INT_FIELD_LENGTH = 4;
51      public static final short MAX_WEIGHT = 256;
52      public static final short MIN_WEIGHT = 1;
53  
54      private static final int MAX_PADDING_LENGTH_LENGTH = 1;
55      public static final int DATA_FRAME_HEADER_LENGTH = FRAME_HEADER_LENGTH + MAX_PADDING_LENGTH_LENGTH;
56      public static final int HEADERS_FRAME_HEADER_LENGTH =
57              FRAME_HEADER_LENGTH + MAX_PADDING_LENGTH_LENGTH + INT_FIELD_LENGTH + 1;
58      public static final int PRIORITY_FRAME_LENGTH = FRAME_HEADER_LENGTH + PRIORITY_ENTRY_LENGTH;
59      public static final int RST_STREAM_FRAME_LENGTH = FRAME_HEADER_LENGTH + INT_FIELD_LENGTH;
60      public static final int PUSH_PROMISE_FRAME_HEADER_LENGTH =
61              FRAME_HEADER_LENGTH + MAX_PADDING_LENGTH_LENGTH + INT_FIELD_LENGTH;
62      public static final int GO_AWAY_FRAME_HEADER_LENGTH = FRAME_HEADER_LENGTH + 2 * INT_FIELD_LENGTH;
63      public static final int WINDOW_UPDATE_FRAME_LENGTH = FRAME_HEADER_LENGTH + INT_FIELD_LENGTH;
64      public static final int CONTINUATION_FRAME_HEADER_LENGTH = FRAME_HEADER_LENGTH + MAX_PADDING_LENGTH_LENGTH;
65  
66      public static final int SETTINGS_HEADER_TABLE_SIZE = 1;
67      public static final int SETTINGS_ENABLE_PUSH = 2;
68      public static final int SETTINGS_MAX_CONCURRENT_STREAMS = 3;
69      public static final int SETTINGS_INITIAL_WINDOW_SIZE = 4;
70      public static final int SETTINGS_MAX_FRAME_SIZE = 5;
71      public static final int SETTINGS_MAX_HEADER_LIST_SIZE = 6;
72  
73      public static final int MAX_HEADER_TABLE_SIZE = Integer.MAX_VALUE; // Size limited by HPACK library
74      public static final long MAX_CONCURRENT_STREAMS = MAX_UNSIGNED_INT;
75      public static final int MAX_INITIAL_WINDOW_SIZE = Integer.MAX_VALUE;
76      public static final int MAX_FRAME_SIZE_LOWER_BOUND = 0x4000;
77      public static final int MAX_FRAME_SIZE_UPPER_BOUND = 0xFFFFFF;
78      public static final long MAX_HEADER_LIST_SIZE = Long.MAX_VALUE;
79  
80      public static final long MIN_HEADER_TABLE_SIZE = 0;
81      public static final long MIN_CONCURRENT_STREAMS = 0;
82      public static final int MIN_INITIAL_WINDOW_SIZE = 0;
83      public static final long MIN_HEADER_LIST_SIZE = 0;
84  
85      public static final int DEFAULT_WINDOW_SIZE = 65535;
86      public static final boolean DEFAULT_ENABLE_PUSH = true;
87      public static final short DEFAULT_PRIORITY_WEIGHT = 16;
88      public static final int DEFAULT_HEADER_TABLE_SIZE = 4096;
89      public static final int DEFAULT_MAX_HEADER_SIZE = 8192;
90      public static final int DEFAULT_MAX_FRAME_SIZE = MAX_FRAME_SIZE_LOWER_BOUND;
91  
92      /**
93       * Indicates whether or not the given value for max frame size falls within the valid range.
94       */
95      public static boolean isMaxFrameSizeValid(int maxFrameSize) {
96          return maxFrameSize >= MAX_FRAME_SIZE_LOWER_BOUND && maxFrameSize <= MAX_FRAME_SIZE_UPPER_BOUND;
97      }
98  
99      /**
100      * Returns a buffer containing the the {@link #CONNECTION_PREFACE}.
101      */
102     public static ByteBuf connectionPrefaceBuf() {
103         // Return a duplicate so that modifications to the reader index will not affect the original buffer.
104         return Unpooled.wrappedBuffer(CONNECTION_PREFACE);
105     }
106 
107     /**
108      * Returns a buffer filled with all zeros that is the appropriate length for a PING frame.
109      */
110     public static ByteBuf emptyPingBuf() {
111         // Return a duplicate so that modifications to the reader index will not affect the original buffer.
112         return Unpooled.wrappedBuffer(EMPTY_PING);
113     }
114 
115     /**
116      * Returns a simple {@link Http2StreamRemovalPolicy} that immediately calls back the
117      * {@link Action} when a stream is marked for removal.
118      */
119     public static Http2StreamRemovalPolicy immediateRemovalPolicy() {
120         return new Http2StreamRemovalPolicy() {
121             private Action action;
122 
123             @Override
124             public void setAction(Action action) {
125                 this.action = checkNotNull(action, "action");
126             }
127 
128             @Override
129             public void markForRemoval(Http2Stream stream) {
130                 if (action == null) {
131                     throw new IllegalStateException(
132                             "Action must be called before removing streams.");
133                 }
134                 action.removeStream(stream);
135             }
136         };
137     }
138 
139     /**
140      * Iteratively looks through the causaility chain for the given exception and returns the first
141      * {@link Http2Exception} or {@code null} if none.
142      */
143     public static Http2Exception getEmbeddedHttp2Exception(Throwable cause) {
144         while (cause != null) {
145             if (cause instanceof Http2Exception) {
146                 return (Http2Exception) cause;
147             }
148             cause = cause.getCause();
149         }
150         return null;
151     }
152 
153     /**
154      * Creates a buffer containing the error message from the given exception. If the cause is
155      * {@code null} returns an empty buffer.
156      */
157     public static ByteBuf toByteBuf(ChannelHandlerContext ctx, Throwable cause) {
158         if (cause == null || cause.getMessage() == null) {
159             return Unpooled.EMPTY_BUFFER;
160         }
161 
162         // Create the debug message.
163         byte[] msg = cause.getMessage().getBytes(UTF_8);
164         ByteBuf debugData = ctx.alloc().buffer(msg.length);
165         debugData.writeBytes(msg);
166         return debugData;
167     }
168 
169     /**
170      * Reads a big-endian (31-bit) integer from the buffer.
171      */
172     public static int readUnsignedInt(ByteBuf buf) {
173         return (buf.readByte() & 0x7F) << 24 | (buf.readByte() & 0xFF) << 16
174                 | (buf.readByte() & 0xFF) << 8 | buf.readByte() & 0xFF;
175     }
176 
177     /**
178      * Writes a big-endian (32-bit) unsigned integer to the buffer.
179      */
180     public static void writeUnsignedInt(long value, ByteBuf out) {
181         out.writeByte((int) (value >> 24 & 0xFF));
182         out.writeByte((int) (value >> 16 & 0xFF));
183         out.writeByte((int) (value >> 8 & 0xFF));
184         out.writeByte((int) (value & 0xFF));
185     }
186 
187     /**
188      * Writes a big-endian (16-bit) unsigned integer to the buffer.
189      */
190     public static void writeUnsignedShort(int value, ByteBuf out) {
191         out.writeByte(value >> 8 & 0xFF);
192         out.writeByte(value & 0xFF);
193     }
194 
195     /**
196      * Writes an HTTP/2 frame header to the output buffer.
197      */
198     public static void writeFrameHeader(ByteBuf out, int payloadLength, byte type,
199             Http2Flags flags, int streamId) {
200         out.ensureWritable(FRAME_HEADER_LENGTH + payloadLength);
201         writeFrameHeaderInternal(out, payloadLength, type, flags, streamId);
202     }
203 
204     static void writeFrameHeaderInternal(ByteBuf out, int payloadLength, byte type,
205             Http2Flags flags, int streamId) {
206         out.writeMedium(payloadLength);
207         out.writeByte(type);
208         out.writeByte(flags.value());
209         out.writeInt(streamId);
210     }
211 
212     /**
213      * Provides the ability to associate the outcome of multiple {@link ChannelPromise}
214      * objects into a single {@link ChannelPromise} object.
215      */
216     static class SimpleChannelPromiseAggregator extends DefaultChannelPromise {
217         private final ChannelPromise promise;
218         private int expectedCount;
219         private int successfulCount;
220         private int failureCount;
221         private boolean doneAllocating;
222 
223         SimpleChannelPromiseAggregator(ChannelPromise promise, Channel c, EventExecutor e) {
224             super(c, e);
225             assert promise != null;
226             this.promise = promise;
227         }
228 
229         /**
230          * Allocate a new promise which will be used to aggregate the overall success of this promise aggregator.
231          * @return A new promise which will be aggregated.
232          * {@code null} if {@link #doneAllocatingPromises()} was previously called.
233          */
234         public ChannelPromise newPromise() {
235             if (doneAllocating) {
236                 throw new IllegalStateException("Done allocating. No more promises can be allocated.");
237             }
238             ++expectedCount;
239             return this;
240         }
241 
242         /**
243          * Signify that no more {@link #newPromise()} allocations will be made.
244          * The aggregation can not be successful until this method is called.
245          * @return The promise that is the aggregation of all promises allocated with {@link #newPromise()}.
246          */
247         public ChannelPromise doneAllocatingPromises() {
248             if (!doneAllocating) {
249                 doneAllocating = true;
250                 if (successfulCount == expectedCount) {
251                     promise.setSuccess();
252                     return super.setSuccess();
253                 }
254             }
255             return this;
256         }
257 
258         @Override
259         public boolean tryFailure(Throwable cause) {
260             if (allowNotificationEvent()) {
261                 ++failureCount;
262                 if (failureCount == 1) {
263                     promise.tryFailure(cause);
264                     return super.tryFailure(cause);
265                 }
266                 // TODO: We break the interface a bit here.
267                 // Multiple failure events can be processed without issue because this is an aggregation.
268                 return true;
269             }
270             return false;
271         }
272 
273         /**
274          * Fail this object if it has not already been failed.
275          * <p>
276          * This method will NOT throw an {@link IllegalStateException} if called multiple times
277          * because that may be expected.
278          */
279         @Override
280         public ChannelPromise setFailure(Throwable cause) {
281             if (allowNotificationEvent()) {
282                 ++failureCount;
283                 if (failureCount == 1) {
284                     promise.setFailure(cause);
285                     return super.setFailure(cause);
286                 }
287             }
288             return this;
289         }
290 
291         private boolean allowNotificationEvent() {
292             return successfulCount + failureCount < expectedCount;
293         }
294 
295         @Override
296         public ChannelPromise setSuccess(Void result) {
297             if (allowNotificationEvent()) {
298                 ++successfulCount;
299                 if (successfulCount == expectedCount && doneAllocating) {
300                     promise.setSuccess(result);
301                     return super.setSuccess(result);
302                 }
303             }
304             return this;
305         }
306 
307         @Override
308         public boolean trySuccess(Void result) {
309             if (allowNotificationEvent()) {
310                 ++successfulCount;
311                 if (successfulCount == expectedCount && doneAllocating) {
312                     promise.trySuccess(result);
313                     return super.trySuccess(result);
314                 }
315                 // TODO: We break the interface a bit here.
316                 // Multiple success events can be processed without issue because this is an aggregation.
317                 return true;
318             }
319             return false;
320         }
321     }
322 
323     /**
324      * Fails the given promise with the cause and then re-throws the cause.
325      */
326     public static <T extends Throwable> T failAndThrow(ChannelPromise promise, T cause) throws T {
327         if (!promise.isDone()) {
328             promise.setFailure(cause);
329         }
330         throw cause;
331     }
332 
333     private Http2CodecUtil() { }
334 }