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