1   
2   
3   
4   
5   
6   
7   
8   
9   
10  
11  
12  
13  
14  
15  
16  package io.netty5.handler.codec.http2;
17  
18  import io.netty5.buffer.api.Buffer;
19  import io.netty5.buffer.api.BufferAllocator;
20  import io.netty5.channel.ChannelHandlerContext;
21  import io.netty5.handler.ssl.ApplicationProtocolNames;
22  import io.netty5.util.AsciiString;
23  import io.netty5.util.concurrent.DefaultPromise;
24  import io.netty5.util.concurrent.EventExecutor;
25  import io.netty5.util.concurrent.Future;
26  import io.netty5.util.concurrent.Promise;
27  import io.netty5.util.internal.UnstableApi;
28  
29  import java.nio.charset.StandardCharsets;
30  import java.util.function.Supplier;
31  
32  import static io.netty5.buffer.api.DefaultBufferAllocators.offHeapAllocator;
33  import static io.netty5.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
34  import static io.netty5.handler.codec.http2.Http2Exception.connectionError;
35  import static io.netty5.handler.codec.http2.Http2Exception.headerListSizeError;
36  import static io.netty5.util.CharsetUtil.UTF_8;
37  import static java.lang.Math.max;
38  import static java.lang.Math.min;
39  import static java.util.concurrent.TimeUnit.MILLISECONDS;
40  import static java.util.concurrent.TimeUnit.SECONDS;
41  
42  
43  
44  
45  @UnstableApi
46  public final class Http2CodecUtil {
47      public static final int CONNECTION_STREAM_ID = 0;
48      public static final int HTTP_UPGRADE_STREAM_ID = 1;
49      public static final CharSequence HTTP_UPGRADE_SETTINGS_HEADER = AsciiString.cached("HTTP2-Settings");
50      public static final CharSequence HTTP_UPGRADE_PROTOCOL_NAME = "h2c";
51      public static final CharSequence TLS_UPGRADE_PROTOCOL_NAME = ApplicationProtocolNames.HTTP_2;
52  
53      public static final int PING_FRAME_PAYLOAD_LENGTH = 8;
54      public static final short MAX_UNSIGNED_BYTE = 0xff;
55      
56  
57  
58  
59      public static final int MAX_PADDING = 256;
60      public static final long MAX_UNSIGNED_INT = 0xffffffffL;
61      public static final int FRAME_HEADER_LENGTH = 9;
62      public static final int SETTING_ENTRY_LENGTH = 6;
63      public static final int PRIORITY_ENTRY_LENGTH = 5;
64      public static final int INT_FIELD_LENGTH = 4;
65      public static final short MAX_WEIGHT = 256;
66      public static final short MIN_WEIGHT = 1;
67  
68      public static final Supplier<Buffer> CONNECTION_PREFACE_BUFFER =
69              offHeapAllocator().constBufferSupplier("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".getBytes(UTF_8));
70  
71      private static final int MAX_PADDING_LENGTH_LENGTH = 1;
72      public static final int DATA_FRAME_HEADER_LENGTH = FRAME_HEADER_LENGTH + MAX_PADDING_LENGTH_LENGTH;
73      public static final int HEADERS_FRAME_HEADER_LENGTH =
74              FRAME_HEADER_LENGTH + MAX_PADDING_LENGTH_LENGTH + INT_FIELD_LENGTH + 1;
75      public static final int PRIORITY_FRAME_LENGTH = FRAME_HEADER_LENGTH + PRIORITY_ENTRY_LENGTH;
76      public static final int RST_STREAM_FRAME_LENGTH = FRAME_HEADER_LENGTH + INT_FIELD_LENGTH;
77      public static final int PUSH_PROMISE_FRAME_HEADER_LENGTH =
78              FRAME_HEADER_LENGTH + MAX_PADDING_LENGTH_LENGTH + INT_FIELD_LENGTH;
79      public static final int GO_AWAY_FRAME_HEADER_LENGTH = FRAME_HEADER_LENGTH + 2 * INT_FIELD_LENGTH;
80      public static final int WINDOW_UPDATE_FRAME_LENGTH = FRAME_HEADER_LENGTH + INT_FIELD_LENGTH;
81      public static final int CONTINUATION_FRAME_HEADER_LENGTH = FRAME_HEADER_LENGTH + MAX_PADDING_LENGTH_LENGTH;
82  
83      public static final char SETTINGS_HEADER_TABLE_SIZE = 1;
84      public static final char SETTINGS_ENABLE_PUSH = 2;
85      public static final char SETTINGS_MAX_CONCURRENT_STREAMS = 3;
86      public static final char SETTINGS_INITIAL_WINDOW_SIZE = 4;
87      public static final char SETTINGS_MAX_FRAME_SIZE = 5;
88      public static final char SETTINGS_MAX_HEADER_LIST_SIZE = 6;
89      public static final int NUM_STANDARD_SETTINGS = 6;
90  
91      public static final long MAX_HEADER_TABLE_SIZE = MAX_UNSIGNED_INT;
92      public static final long MAX_CONCURRENT_STREAMS = MAX_UNSIGNED_INT;
93      public static final int MAX_INITIAL_WINDOW_SIZE = Integer.MAX_VALUE;
94      public static final int MAX_FRAME_SIZE_LOWER_BOUND = 0x4000;
95      public static final int MAX_FRAME_SIZE_UPPER_BOUND = 0xffffff;
96      public static final long MAX_HEADER_LIST_SIZE = MAX_UNSIGNED_INT;
97  
98      public static final long MIN_HEADER_TABLE_SIZE = 0;
99      public static final long MIN_CONCURRENT_STREAMS = 0;
100     public static final int MIN_INITIAL_WINDOW_SIZE = 0;
101     public static final long MIN_HEADER_LIST_SIZE = 0;
102 
103     public static final int DEFAULT_WINDOW_SIZE = 65535;
104     public static final short DEFAULT_PRIORITY_WEIGHT = 16;
105     public static final int DEFAULT_HEADER_TABLE_SIZE = 4096;
106     
107 
108 
109 
110 
111     public static final long DEFAULT_HEADER_LIST_SIZE = 8192;
112     public static final int DEFAULT_MAX_FRAME_SIZE = MAX_FRAME_SIZE_LOWER_BOUND;
113     
114 
115 
116 
117     public static final int SMALLEST_MAX_CONCURRENT_STREAMS = 100;
118     static final int DEFAULT_MAX_RESERVED_STREAMS = SMALLEST_MAX_CONCURRENT_STREAMS;
119     static final int DEFAULT_MIN_ALLOCATION_CHUNK = 1024;
120 
121     
122 
123 
124 
125 
126 
127 
128     public static long calculateMaxHeaderListSizeGoAway(long maxHeaderListSize) {
129         
130         return maxHeaderListSize + (maxHeaderListSize >>> 2);
131     }
132 
133     public static final long DEFAULT_GRACEFUL_SHUTDOWN_TIMEOUT_MILLIS = MILLISECONDS.convert(30, SECONDS);
134 
135     public static final int DEFAULT_MAX_QUEUED_CONTROL_FRAMES = 10000;
136 
137     
138 
139 
140 
141 
142 
143     public static boolean isOutboundStream(boolean server, int streamId) {
144         boolean even = (streamId & 1) == 0;
145         return streamId > 0 && server == even;
146     }
147 
148     
149 
150 
151     public static boolean isStreamIdValid(int streamId) {
152         return streamId >= 0;
153     }
154 
155     static boolean isStreamIdValid(int streamId, boolean server) {
156         return isStreamIdValid(streamId) && server == ((streamId & 1) == 0);
157     }
158 
159     
160 
161 
162     public static boolean isMaxFrameSizeValid(int maxFrameSize) {
163         return maxFrameSize >= MAX_FRAME_SIZE_LOWER_BOUND && maxFrameSize <= MAX_FRAME_SIZE_UPPER_BOUND;
164     }
165 
166     
167 
168 
169     public static Buffer connectionPrefaceBuffer() {
170         
171         return CONNECTION_PREFACE_BUFFER.get();
172     }
173 
174     
175 
176 
177 
178     public static Http2Exception getEmbeddedHttp2Exception(Throwable cause) {
179         while (cause != null) {
180             if (cause instanceof Http2Exception) {
181                 return (Http2Exception) cause;
182             }
183             cause = cause.getCause();
184         }
185         return null;
186     }
187 
188     
189 
190 
191 
192     public static Buffer toBuffer(ChannelHandlerContext ctx, Throwable cause) {
193         BufferAllocator allocator = ctx.bufferAllocator();
194         if (cause == null || cause.getMessage() == null) {
195             return allocator.allocate(0);
196         }
197 
198         return allocator.copyOf(cause.getMessage().getBytes(StandardCharsets.UTF_8));
199     }
200 
201     
202 
203 
204     public static int readUnsignedInt(Buffer buf) {
205         return buf.readInt() & 0x7fffffff;
206     }
207 
208     
209 
210 
211     public static void writeFrameHeader(Buffer out, int payloadLength, byte type,
212             Http2Flags flags, int streamId) {
213         out.ensureWritable(FRAME_HEADER_LENGTH + payloadLength);
214         writeFrameHeaderInternal(out, payloadLength, type, flags, streamId);
215     }
216 
217     
218 
219 
220     public static int streamableBytes(StreamByteDistributor.StreamState state) {
221         return max(0, (int) min(state.pendingBytes(), state.windowSize()));
222     }
223 
224     
225 
226 
227 
228 
229 
230 
231 
232     public static void headerListSizeExceeded(int streamId, long maxHeaderListSize,
233                                               boolean onDecode) throws Http2Exception {
234         throw headerListSizeError(streamId, PROTOCOL_ERROR, onDecode, "Header size exceeded max " +
235                                   "allowed size (%d)", maxHeaderListSize);
236     }
237 
238     
239 
240 
241 
242 
243 
244 
245     public static void headerListSizeExceeded(long maxHeaderListSize) throws Http2Exception {
246         throw connectionError(PROTOCOL_ERROR, "Header size exceeded max " +
247                 "allowed size (%d)", maxHeaderListSize);
248     }
249 
250     static void writeFrameHeaderInternal(Buffer out, int payloadLength, byte type, Http2Flags flags, int streamId) {
251         out.writeMedium(payloadLength);
252         out.writeByte(type);
253         out.writeByte((byte) flags.value());
254         out.writeInt(streamId);
255     }
256 
257     
258 
259 
260 
261     static final class SimpleChannelPromiseAggregator extends DefaultPromise<Void> {
262         private final Promise<Void> promise;
263         private int expectedCount;
264         private int doneCount;
265         private Throwable aggregateFailure;
266         private boolean doneAllocating;
267 
268         SimpleChannelPromiseAggregator(Promise<Void> promise, EventExecutor e) {
269             super(e);
270             assert promise != null && !promise.isDone();
271             this.promise = promise;
272         }
273 
274         
275 
276 
277 
278 
279         public Promise<Void> newPromise() {
280             assert !doneAllocating : "Done allocating. No more promises can be allocated.";
281             ++expectedCount;
282             return this;
283         }
284 
285         
286 
287 
288 
289 
290         public Future<Void> doneAllocatingPromises() {
291             if (!doneAllocating) {
292                 doneAllocating = true;
293                 if (doneCount == expectedCount || expectedCount == 0) {
294                     return setPromise().asFuture();
295                 }
296             }
297             return this;
298         }
299 
300         @Override
301         public boolean tryFailure(Throwable cause) {
302             if (allowFailure()) {
303                 ++doneCount;
304                 setAggregateFailure(cause);
305                 if (allPromisesDone()) {
306                     return tryPromise();
307                 }
308                 
309                 
310                 return true;
311             }
312             return false;
313         }
314 
315         
316 
317 
318 
319 
320 
321         @Override
322         public Promise<Void> setFailure(Throwable cause) {
323             if (allowFailure()) {
324                 ++doneCount;
325                 setAggregateFailure(cause);
326                 if (allPromisesDone()) {
327                     setPromise();
328                 }
329             }
330             return this;
331         }
332 
333         @Override
334         public Promise<Void> setSuccess(Void result) {
335             if (awaitingPromises()) {
336                 ++doneCount;
337                 if (allPromisesDone()) {
338                     setPromise();
339                 }
340             }
341             return this;
342         }
343 
344         @Override
345         public boolean trySuccess(Void result) {
346             if (awaitingPromises()) {
347                 ++doneCount;
348                 if (allPromisesDone()) {
349                     return tryPromise();
350                 }
351                 
352                 
353                 return true;
354             }
355             return false;
356         }
357 
358         private boolean allowFailure() {
359             return awaitingPromises() || expectedCount == 0;
360         }
361 
362         private boolean awaitingPromises() {
363             return doneCount < expectedCount;
364         }
365 
366         private boolean allPromisesDone() {
367             return doneCount == expectedCount && doneAllocating;
368         }
369 
370         private Promise<Void> setPromise() {
371             if (aggregateFailure == null) {
372                 promise.setSuccess(null);
373                 super.setSuccess(null);
374             } else {
375                 promise.setFailure(aggregateFailure);
376                 super.setFailure(aggregateFailure);
377             }
378             return this;
379         }
380 
381         private boolean tryPromise() {
382             if (aggregateFailure == null) {
383                 promise.trySuccess(null);
384                 return super.trySuccess(null);
385             } else {
386                 promise.tryFailure(aggregateFailure);
387                 return super.tryFailure(aggregateFailure);
388             }
389         }
390 
391         private void setAggregateFailure(Throwable cause) {
392             if (aggregateFailure == null) {
393                 aggregateFailure = cause;
394             }
395         }
396     }
397 
398     public static void verifyPadding(int padding) {
399         if (padding < 0 || padding > MAX_PADDING) {
400             throw new IllegalArgumentException(String.format("Invalid padding '%d'. Padding must be between 0 and " +
401                                                              "%d (inclusive).", padding, MAX_PADDING));
402         }
403     }
404     private Http2CodecUtil() { }
405 }