1 /*
2 * Copyright 2020 The Netty Project
3 *
4 * The Netty Project licenses this file to you under the Apache License,
5 * version 2.0 (the "License"); you may not use this file except in compliance
6 * with the License. You may obtain a 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
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations
14 * under the License.
15 */
16 package io.netty.handler.codec.quic;
17
18 import io.netty.channel.ChannelHandler;
19
20 import java.util.Objects;
21 import java.util.concurrent.Executor;
22 import java.util.concurrent.TimeUnit;
23 import java.util.function.Function;
24
25 import static io.netty.util.internal.ObjectUtil.checkInRange;
26 import static io.netty.util.internal.ObjectUtil.checkPositive;
27 import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;
28
29 /**
30 * Abstract base class for {@code QUIC} codec builders.
31 *
32 * @param <B> the type of the {@link QuicCodecBuilder}.
33 */
34 public abstract class QuicCodecBuilder<B extends QuicCodecBuilder<B>> {
35 private final boolean server;
36 private Boolean grease;
37 private Long maxIdleTimeout;
38 private Long maxRecvUdpPayloadSize;
39 private Long maxSendUdpPayloadSize;
40 private Long initialMaxData;
41 private Long initialMaxStreamDataBidiLocal;
42 private Long initialMaxStreamDataBidiRemote;
43 private Long initialMaxStreamDataUni;
44 private Long initialMaxStreamsBidi;
45 private Long initialMaxStreamsUni;
46 private Long ackDelayExponent;
47 private Long maxAckDelay;
48 private Boolean disableActiveMigration;
49 private Boolean enableHystart;
50 private QuicCongestionControlAlgorithm congestionControlAlgorithm;
51 private Integer initialCongestionWindowPackets;
52 private int localConnIdLength;
53 private Function<QuicChannel, ? extends QuicSslEngine> sslEngineProvider;
54 private FlushStrategy flushStrategy = FlushStrategy.DEFAULT;
55 private Integer recvQueueLen;
56 private Integer sendQueueLen;
57 private Long activeConnectionIdLimit;
58 private byte[] statelessResetToken;
59
60 private Executor sslTaskExecutor;
61
62 // package-private for testing only
63 int version;
64
65 QuicCodecBuilder(boolean server) {
66 Quic.ensureAvailability();
67 this.version = Quiche.QUICHE_PROTOCOL_VERSION;
68 this.localConnIdLength = Quiche.QUICHE_MAX_CONN_ID_LEN;
69 this.server = server;
70 }
71
72 QuicCodecBuilder(QuicCodecBuilder<B> builder) {
73 Quic.ensureAvailability();
74 this.server = builder.server;
75 this.grease = builder.grease;
76 this.maxIdleTimeout = builder.maxIdleTimeout;
77 this.maxRecvUdpPayloadSize = builder.maxRecvUdpPayloadSize;
78 this.maxSendUdpPayloadSize = builder.maxSendUdpPayloadSize;
79 this.initialMaxData = builder.initialMaxData;
80 this.initialMaxStreamDataBidiLocal = builder.initialMaxStreamDataBidiLocal;
81 this.initialMaxStreamDataBidiRemote = builder.initialMaxStreamDataBidiRemote;
82 this.initialMaxStreamDataUni = builder.initialMaxStreamDataUni;
83 this.initialMaxStreamsBidi = builder.initialMaxStreamsBidi;
84 this.initialMaxStreamsUni = builder.initialMaxStreamsUni;
85 this.ackDelayExponent = builder.ackDelayExponent;
86 this.maxAckDelay = builder.maxAckDelay;
87 this.disableActiveMigration = builder.disableActiveMigration;
88 this.enableHystart = builder.enableHystart;
89 this.congestionControlAlgorithm = builder.congestionControlAlgorithm;
90 this.initialCongestionWindowPackets = builder.initialCongestionWindowPackets;
91 this.localConnIdLength = builder.localConnIdLength;
92 this.sslEngineProvider = builder.sslEngineProvider;
93 this.flushStrategy = builder.flushStrategy;
94 this.recvQueueLen = builder.recvQueueLen;
95 this.sendQueueLen = builder.sendQueueLen;
96 this.activeConnectionIdLimit = builder.activeConnectionIdLimit;
97 this.statelessResetToken = builder.statelessResetToken;
98 this.sslTaskExecutor = builder.sslTaskExecutor;
99 this.version = builder.version;
100 }
101
102 /**
103 * Returns itself.
104 *
105 * @return itself.
106 */
107 @SuppressWarnings("unchecked")
108 protected final B self() {
109 return (B) this;
110 }
111
112 /**
113 * Sets the {@link FlushStrategy} that will be used to detect when an automatic flush
114 * should happen.
115 *
116 * @param flushStrategy the strategy to use.
117 * @return the instance itself.
118 */
119 public final B flushStrategy(FlushStrategy flushStrategy) {
120 this.flushStrategy = Objects.requireNonNull(flushStrategy, "flushStrategy");
121 return self();
122 }
123
124 /**
125 * Sets the congestion control algorithm to use.
126 *
127 * The default is {@link QuicCongestionControlAlgorithm#CUBIC}.
128 *
129 * @param congestionControlAlgorithm the {@link QuicCongestionControlAlgorithm} to use.
130 * @return the instance itself.
131 */
132 public final B congestionControlAlgorithm(QuicCongestionControlAlgorithm congestionControlAlgorithm) {
133 this.congestionControlAlgorithm = congestionControlAlgorithm;
134 return self();
135 }
136
137 /**
138 * Sets initial congestion window size in terms of packet count.
139 *
140 * The default value is 10.
141 *
142 * @param numPackets number of packets for the initial congestion window
143 * @return
144 */
145 public final B initialCongestionWindowPackets(int numPackets) {
146 this.initialCongestionWindowPackets = numPackets;
147 return self();
148 }
149
150 /**
151 * Set if <a href="https://tools.ietf.org/html/draft-thomson-quic-bit-grease-00">greasing</a> should be enabled
152 * or not.
153 *
154 * The default value is {@code true}.
155 *
156 * @param enable {@code true} if enabled, {@code false} otherwise.
157 * @return the instance itself.
158 */
159 public final B grease(boolean enable) {
160 grease = enable;
161 return self();
162 }
163
164 /**
165 * See <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_max_idle_timeout">
166 * set_max_idle_timeout</a>.
167 *
168 * The default value is infinite, that is, no timeout is used.
169 *
170 * @param amount the maximum idle timeout.
171 * @param unit the {@link TimeUnit}.
172 * @return the instance itself.
173 */
174 public final B maxIdleTimeout(long amount, TimeUnit unit) {
175 this.maxIdleTimeout = unit.toMillis(checkPositiveOrZero(amount, "amount"));
176 return self();
177 }
178
179 /**
180 * See <a href="https://github.com/cloudflare/quiche/blob/35e38d987c1e53ef2bd5f23b754c50162b5adac8/src/lib.rs#L669">
181 * set_max_send_udp_payload_size</a>.
182 *
183 * The default and minimum value is 1200.
184 *
185 * @param size the maximum payload size that is advertised to the remote peer.
186 * @return the instance itself.
187 */
188 public final B maxSendUdpPayloadSize(long size) {
189 this.maxSendUdpPayloadSize = checkPositiveOrZero(size, "value");
190 return self();
191 }
192
193 /**
194 * See <a href="https://github.com/cloudflare/quiche/blob/35e38d987c1e53ef2bd5f23b754c50162b5adac8/src/lib.rs#L662">
195 * set_max_recv_udp_payload_size</a>.
196 *
197 * The default value is 65527.
198 *
199 * @param size the maximum payload size that is advertised to the remote peer.
200 * @return the instance itself.
201 */
202 public final B maxRecvUdpPayloadSize(long size) {
203 this.maxRecvUdpPayloadSize = checkPositiveOrZero(size, "value");
204 return self();
205 }
206
207 /**
208 * See <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_initial_max_data">
209 * set_initial_max_data</a>.
210 *
211 * The default value is 0.
212 *
213 * @param value the initial maximum data limit.
214 * @return the instance itself.
215 */
216 public final B initialMaxData(long value) {
217 this.initialMaxData = checkPositiveOrZero(value, "value");
218 return self();
219 }
220
221 /**
222 * See
223 * <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_initial_max_stream_data_bidi_local">
224 * set_initial_max_stream_data_bidi_local</a>.
225 *
226 * The default value is 0.
227 *
228 * @param value the initial maximum data limit for local bidirectional streams.
229 * @return the instance itself.
230 */
231 public final B initialMaxStreamDataBidirectionalLocal(long value) {
232 this.initialMaxStreamDataBidiLocal = checkPositiveOrZero(value, "value");
233 return self();
234 }
235
236 /**
237 * See
238 * <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_initial_max_stream_data_bidi_remote">
239 * set_initial_max_stream_data_bidi_remote</a>.
240 *
241 * The default value is 0.
242 *
243 * @param value the initial maximum data limit for remote bidirectional streams.
244 * @return the instance itself.
245 */
246 public final B initialMaxStreamDataBidirectionalRemote(long value) {
247 this.initialMaxStreamDataBidiRemote = checkPositiveOrZero(value, "value");
248 return self();
249 }
250
251 /**
252 * See
253 * <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_initial_max_stream_data_uni">
254 * set_initial_max_stream_data_uni</a>.
255 *
256 * The default value is 0.
257 *
258 * @param value the initial maximum data limit for unidirectional streams.
259 * @return the instance itself.
260 */
261 public final B initialMaxStreamDataUnidirectional(long value) {
262 this.initialMaxStreamDataUni = checkPositiveOrZero(value, "value");
263 return self();
264 }
265
266 /**
267 * See
268 * <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_initial_max_streams_bidi">
269 * set_initial_max_streams_bidi</a>.
270 *
271 * The default value is 0.
272 *
273 * @param value the initial maximum stream limit for bidirectional streams.
274 * @return the instance itself.
275 */
276 public final B initialMaxStreamsBidirectional(long value) {
277 this.initialMaxStreamsBidi = checkPositiveOrZero(value, "value");
278 return self();
279 }
280
281 /**
282 * See
283 * <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_initial_max_streams_uni">
284 * set_initial_max_streams_uni</a>.
285 *
286 * The default value is 0.
287 *
288 * @param value the initial maximum stream limit for unidirectional streams.
289 * @return the instance itself.
290 */
291 public final B initialMaxStreamsUnidirectional(long value) {
292 this.initialMaxStreamsUni = checkPositiveOrZero(value, "value");
293 return self();
294 }
295
296 /**
297 * See
298 * <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_ack_delay_exponent">
299 * set_ack_delay_exponent</a>.
300 *
301 * The default value is 3.
302 *
303 * @param value the delay exponent used for ACKs.
304 * @return the instance itself.
305 */
306 public final B ackDelayExponent(long value) {
307 this.ackDelayExponent = checkPositiveOrZero(value, "value");
308 return self();
309 }
310
311 /**
312 * See
313 * <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_max_ack_delay">
314 * set_max_ack_delay</a>.
315 *
316 * The default value is 25 milliseconds.
317 *
318 * @param amount the max ack delay.
319 * @param unit the {@link TimeUnit}.
320 * @return the instance itself.
321 */
322 public final B maxAckDelay(long amount, TimeUnit unit) {
323 this.maxAckDelay = unit.toMillis(checkPositiveOrZero(amount, "amount"));
324 return self();
325 }
326
327 /**
328 * See
329 * <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_disable_active_migration">
330 * set_disable_active_migration</a>.
331 *
332 * The default value is {@code true}.
333 *
334 * @param enable {@code true} if migration should be enabled, {@code false} otherwise.
335 * @return the instance itself.
336 */
337 public final B activeMigration(boolean enable) {
338 this.disableActiveMigration = !enable;
339 return self();
340 }
341
342 /**
343 * See
344 * <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.enable_hystart">
345 * enable_hystart</a>.
346 *
347 * The default value is {@code true}.
348 *
349 * @param enable {@code true} if Hystart should be enabled.
350 * @return the instance itself.
351 */
352 public final B hystart(boolean enable) {
353 this.enableHystart = enable;
354 return self();
355 }
356
357 /**
358 * Sets the local connection id length that is used.
359 *
360 * The default is 20, which is also the maximum that is supported.
361 *
362 * @param value the length of local generated connections ids.
363 * @return the instance itself.
364 */
365 public final B localConnectionIdLength(int value) {
366 this.localConnIdLength = checkInRange(value, 0, Quiche.QUICHE_MAX_CONN_ID_LEN, "value");
367 return self();
368 }
369
370 /**
371 * Allows to configure the {@code QUIC version} that should be used.
372 *
373 * The default value is the latest supported version by the underlying library.
374 *
375 * @param version the {@code QUIC version} to use.
376 * @return the instance itself.
377 */
378 public final B version(int version) {
379 this.version = version;
380 return self();
381 }
382
383 /**
384 * If configured this will enable <a href="https://tools.ietf.org/html/draft-ietf-quic-datagram-01">
385 * Datagram support.</a>
386 * @param recvQueueLen the RECV queue length.
387 * @param sendQueueLen the SEND queue length.
388 * @return the instance itself.
389 */
390 public final B datagram(int recvQueueLen, int sendQueueLen) {
391 checkPositive(recvQueueLen, "recvQueueLen");
392 checkPositive(sendQueueLen, "sendQueueLen");
393
394 this.recvQueueLen = recvQueueLen;
395 this.sendQueueLen = sendQueueLen;
396 return self();
397 }
398
399 /**
400 * The {@link QuicSslContext} that will be used to create {@link QuicSslEngine}s for {@link QuicChannel}s.
401 *
402 * If you need a more flexible way to provide {@link QuicSslEngine}s use {@link #sslEngineProvider(Function)}.
403 *
404 * @param sslContext the context.
405 * @return the instance itself.
406 */
407 public final B sslContext(QuicSslContext sslContext) {
408 if (server != sslContext.isServer()) {
409 throw new IllegalArgumentException("QuicSslContext.isServer() " + sslContext.isServer()
410 + " isn't supported by this builder");
411 }
412 return sslEngineProvider(q -> sslContext.newEngine(q.alloc()));
413 }
414
415 /**
416 * The {@link Function} that will return the {@link QuicSslEngine} that should be used for the
417 * {@link QuicChannel}.
418 *
419 * @param sslEngineProvider the provider.
420 * @return the instance itself.
421 */
422 public final B sslEngineProvider(Function<QuicChannel, ? extends QuicSslEngine> sslEngineProvider) {
423 this.sslEngineProvider = sslEngineProvider;
424 return self();
425 }
426
427 /**
428 * Allow to configure a {@link Executor} that will be used to run expensive SSL operations.
429 *
430 * @param sslTaskExecutor the {@link Executor} that will be used to offload expensive SSL operations.
431 * @return the instance itself.
432 */
433 public final B sslTaskExecutor(Executor sslTaskExecutor) {
434 this.sslTaskExecutor = sslTaskExecutor;
435 return self();
436 }
437
438 /**
439 * Allows to configure the {@code active connect id limit} that should be used.
440 *
441 * @param limit the limit to use.
442 * @return the instance itself.
443 */
444 public final B activeConnectionIdLimit(long limit) {
445 checkPositive(limit, "limit");
446 activeConnectionIdLimit = limit;
447 return self();
448 }
449
450 /**
451 * Allows to configure the {@code active connect id limit} that should be used.
452 *
453 * @param token the token to use.
454 * @return the instance itself.
455 */
456 public final B statelessResetToken(byte[] token) {
457 if (token.length != 16) {
458 throw new IllegalArgumentException("token must be 16 bytes but was " + token.length);
459 }
460
461 this.statelessResetToken = token.clone();
462 return self();
463 }
464
465 private QuicheConfig createConfig() {
466 return new QuicheConfig(version, grease,
467 maxIdleTimeout, maxSendUdpPayloadSize, maxRecvUdpPayloadSize, initialMaxData,
468 initialMaxStreamDataBidiLocal, initialMaxStreamDataBidiRemote,
469 initialMaxStreamDataUni, initialMaxStreamsBidi, initialMaxStreamsUni,
470 ackDelayExponent, maxAckDelay, disableActiveMigration, enableHystart,
471 congestionControlAlgorithm, initialCongestionWindowPackets, recvQueueLen, sendQueueLen,
472 activeConnectionIdLimit, statelessResetToken);
473 }
474
475 /**
476 * Validate the configuration before building the codec.
477 */
478 protected void validate() {
479 if (sslEngineProvider == null) {
480 throw new IllegalStateException("sslEngineProvider can't be null");
481 }
482 }
483
484 /**
485 * Builds the QUIC codec that should be added to the {@link io.netty.channel.ChannelPipeline} of the underlying
486 * {@link io.netty.channel.Channel} which is used as transport for QUIC.
487 *
488 * @return the {@link ChannelHandler} which acts as QUIC codec.
489 */
490 public final ChannelHandler build() {
491 validate();
492 QuicheConfig config = createConfig();
493 try {
494 return build(config, sslEngineProvider, sslTaskExecutor, localConnIdLength, flushStrategy);
495 } catch (Throwable cause) {
496 config.free();
497 throw cause;
498 }
499 }
500
501 /**
502 * Clone the builder
503 *
504 * @return the new instance that is a clone if this instance.
505 */
506 public abstract B clone();
507
508 /**
509 * Builds the QUIC codec.
510 *
511 * @param config the {@link QuicheConfig} that should be used.
512 * @param sslContextProvider the context provider
513 * @param sslTaskExecutor the {@link Executor} to use.
514 * @param localConnIdLength the local connection id length.
515 * @param flushStrategy the {@link FlushStrategy} that should be used.
516 * @return the {@link ChannelHandler} which acts as codec.
517 */
518 abstract ChannelHandler build(QuicheConfig config,
519 Function<QuicChannel, ? extends QuicSslEngine> sslContextProvider,
520 Executor sslTaskExecutor,
521 int localConnIdLength, FlushStrategy flushStrategy);
522 }