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