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 }