View Javadoc
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 }