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.Channel;
19  import io.netty.channel.ChannelFuture;
20  import io.netty.channel.ChannelHandler;
21  import io.netty.channel.ChannelOption;
22  import io.netty.channel.EventLoop;
23  import io.netty.util.AttributeKey;
24  import io.netty.util.concurrent.Future;
25  import io.netty.util.concurrent.Promise;
26  import io.netty.util.internal.ObjectUtil;
27  import io.netty.util.internal.logging.InternalLogger;
28  import io.netty.util.internal.logging.InternalLoggerFactory;
29  import org.jetbrains.annotations.Nullable;
30  
31  import java.net.InetSocketAddress;
32  import java.net.SocketAddress;
33  import java.util.HashMap;
34  import java.util.LinkedHashMap;
35  import java.util.Map;
36  
37  /**
38   * Bootstrap that helps to bootstrap {@link QuicChannel}s and connecting these to remote peers.
39   */
40  public final class QuicChannelBootstrap {
41      private static final InternalLogger logger = InternalLoggerFactory.getInstance(QuicChannelBootstrap.class);
42  
43      private final Channel parent;
44      // The order in which ChannelOptions are applied is important they may depend on each other for validation
45      // purposes.
46      private final Map<ChannelOption<?>, Object> options = new LinkedHashMap<>();
47      private final Map<AttributeKey<?>, Object> attrs = new HashMap<>();
48      private final Map<ChannelOption<?>, Object> streamOptions = new LinkedHashMap<>();
49      private final Map<AttributeKey<?>, Object> streamAttrs = new HashMap<>();
50  
51      private SocketAddress local;
52      private SocketAddress remote;
53      private QuicConnectionAddress localAddress = QuicConnectionAddress.EPHEMERAL;
54      private QuicConnectionAddress remoteAddress = QuicConnectionAddress.EPHEMERAL;
55      private ChannelHandler handler;
56      private ChannelHandler streamHandler;
57  
58      /**
59       * Creates a new instance which uses the given {@link Channel} to bootstrap the {@link QuicChannel}.
60       * This {@link io.netty.channel.ChannelPipeline} of the {@link Channel} needs to have the quic codec in the
61       * pipeline.
62       *
63       * @param parent    the {@link Channel} that is used as the transport layer.
64       * @deprecated Use QuicChannel.newBootstrap() instead.
65       */
66      @Deprecated
67      public QuicChannelBootstrap(Channel parent) {
68          Quic.ensureAvailability();
69          this.parent = ObjectUtil.checkNotNull(parent, "parent");
70      }
71  
72      /**
73       * Allow to specify a {@link ChannelOption} which is used for the {@link QuicChannel} instances once they got
74       * created. Use a value of {@code null} to remove a previous set {@link ChannelOption}.
75       *
76       * @param option    the {@link ChannelOption} to apply to the {@link QuicChannel}.
77       * @param value     the value of the option.
78       * @param <T>       the type of the value.
79       * @return          this instance.
80       */
81      public <T> QuicChannelBootstrap option(ChannelOption<T> option, @Nullable T value) {
82          Quic.updateOptions(options, option, value);
83          return this;
84      }
85  
86      /**
87       * Allow to specify an initial attribute of the newly created {@link QuicChannel}.  If the {@code value} is
88       * {@code null}, the attribute of the specified {@code key} is removed.
89       *
90       * @param key       the {@link AttributeKey} to apply to the {@link QuicChannel}.
91       * @param value     the value of the attribute.
92       * @param <T>       the type of the value.
93       * @return          this instance.
94       */
95      public <T> QuicChannelBootstrap attr(AttributeKey<T> key, @Nullable T value) {
96          Quic.updateAttributes(attrs, key, value);
97          return this;
98      }
99  
100     /**
101      * Set the {@link ChannelHandler} that is added to the {@link io.netty.channel.ChannelPipeline} of the
102      * {@link QuicChannel} once created.
103      *
104      * @param handler   the {@link ChannelHandler} that is added to the {@link QuicChannel}s
105      *                  {@link io.netty.channel.ChannelPipeline}.
106      * @return          this instance.
107      */
108     public QuicChannelBootstrap handler(ChannelHandler handler) {
109         this.handler = ObjectUtil.checkNotNull(handler, "handler");
110         return this;
111     }
112 
113     /**
114      * Allow to specify a {@link ChannelOption} which is used for the {@link QuicStreamChannel} instances once they got
115      * created. Use a value of {@code null} to remove a previous set {@link ChannelOption}.
116      *
117      * @param option    the {@link ChannelOption} to apply to the {@link QuicStreamChannel}s.
118      * @param value     the value of the option.
119      * @param <T>       the type of the value.
120      * @return          this instance.
121      */
122     public <T> QuicChannelBootstrap streamOption(ChannelOption<T> option, @Nullable T value) {
123         Quic.updateOptions(streamOptions, option, value);
124         return this;
125     }
126 
127     /**
128      * Allow to specify an initial attribute of the newly created {@link QuicStreamChannel}. If the {@code value} is
129      * {@code null}, the attribute of the specified {@code key} is removed.
130      *
131      * @param key       the {@link AttributeKey} to apply to the {@link QuicStreamChannel}s.
132      * @param value     the value of the attribute.
133      * @param <T>       the type of the value.
134      * @return          this instance.
135      */
136     public <T> QuicChannelBootstrap streamAttr(AttributeKey<T> key, @Nullable T value) {
137         Quic.updateAttributes(streamAttrs, key, value);
138         return this;
139     }
140 
141     /**
142      * Set the {@link ChannelHandler} that is added to the {@link io.netty.channel.ChannelPipeline} of the
143      * {@link QuicStreamChannel} once created.
144      *
145      * @param streamHandler     the {@link ChannelHandler} that is added to the {@link QuicStreamChannel}s
146      *                          {@link io.netty.channel.ChannelPipeline}.
147      * @return                  this instance.
148      */
149     public QuicChannelBootstrap streamHandler(ChannelHandler streamHandler) {
150         this.streamHandler = ObjectUtil.checkNotNull(streamHandler, "streamHandler");
151         return this;
152     }
153 
154     /**
155      * Set the local address.
156      *
157      * @param local    the {@link SocketAddress} of the local peer.
158      * @return          this instance.
159      */
160     public QuicChannelBootstrap localAddress(SocketAddress local) {
161         this.local = ObjectUtil.checkNotNull(local, "local");
162         return this;
163     }
164 
165     /**
166      * Set the remote address of the host to talk to.
167      *
168      * @param remote    the {@link SocketAddress} of the remote peer.
169      * @return          this instance.
170      */
171     public QuicChannelBootstrap remoteAddress(SocketAddress remote) {
172         this.remote = ObjectUtil.checkNotNull(remote, "remote");
173         return this;
174     }
175 
176     /**
177      * Set the {@link QuicConnectionAddress} to use. If none is specified a random address is generated on your
178      * behalf.
179      *
180      * @param connectionAddress     the {@link QuicConnectionAddress} to use.
181      * @return                      this instance.
182      * @deprecated                  use {@link #localConnectionAddress(QuicConnectionAddress)}.
183      */
184     @Deprecated
185     public QuicChannelBootstrap connectionAddress(QuicConnectionAddress connectionAddress) {
186         this.localAddress = ObjectUtil.checkNotNull(connectionAddress, "connectionAddress");
187         return this;
188     }
189 
190     /**
191      * Set the {@link QuicConnectionAddress} to use. If none is specified a random address is generated on your
192      * behalf.
193      *
194      * @param localConnectionAddress     the {@link QuicConnectionAddress} to use.
195      * @return                      this instance.
196      */
197     public QuicChannelBootstrap localConnectionAddress(QuicConnectionAddress localConnectionAddress) {
198         this.localAddress = ObjectUtil.checkNotNull(localConnectionAddress, "localConnectionAddress");
199         return this;
200     }
201 
202     /**
203      * Set the {@link QuicConnectionAddress} to use. If none is specified a random address is generated on your
204      * behalf. Be aware that the provided {@link QuicConnectionAddress} should be generated carefully in a random way
205      * that makes it impossible to predict it as otherwise the security of the quic connection might be too weak.
206      * Fore more details see
207      * <a href="https://datatracker.ietf.org/doc/html/rfc9000#section-7.2">RFC9000 Section 7.2</a>.
208      *
209      * @param remoteConnectionAddress     the {@link QuicConnectionAddress} to use.
210      * @return                            this instance.
211      */
212     public QuicChannelBootstrap remoteConnectionAddress(QuicConnectionAddress remoteConnectionAddress) {
213         this.remoteAddress = ObjectUtil.checkNotNull(remoteConnectionAddress, "remoteConnectionAddress");
214         return this;
215     }
216 
217     /**
218      * Connects a {@link QuicChannel} to the remote peer and notifies the future once done.
219      *
220      * @return {@link Future} which is notified once the operation completes.
221      */
222     public Future<QuicChannel> connect() {
223         return connect(parent.eventLoop().newPromise());
224     }
225 
226     /**
227      * Connects a {@link QuicChannel} to the remote peer and notifies the promise once done.
228      *
229      * @param promise   the {@link Promise} which is notified once the operations completes.
230      * @return          {@link Future} which is notified once the operation completes.
231 
232      */
233     public Future<QuicChannel> connect(Promise<QuicChannel> promise) {
234         if (handler == null && streamHandler == null) {
235             throw new IllegalStateException("handler and streamHandler not set");
236         }
237         SocketAddress local = this.local;
238         if (local == null) {
239             local = parent.localAddress();
240         }
241         if (local == null) {
242             local = new InetSocketAddress(0);
243         }
244 
245         SocketAddress remote = this.remote;
246         if (remote == null) {
247             remote = parent.remoteAddress();
248         }
249         if (remote == null) {
250             throw new IllegalStateException("remote not set");
251         }
252 
253         final QuicConnectionAddress localaddress = localAddress;
254         final QuicConnectionAddress remoteaddress = remoteAddress;
255         QuicChannel channel = QuicheQuicChannel.forClient(parent, (InetSocketAddress)  local,
256                 (InetSocketAddress) remote,
257                 streamHandler, Quic.toOptionsArray(streamOptions), Quic.toAttributesArray(streamAttrs));
258 
259         Quic.setupChannel(channel, Quic.toOptionsArray(options), Quic.toAttributesArray(attrs), handler, logger);
260         EventLoop eventLoop = parent.eventLoop();
261         eventLoop.register(channel).addListener((ChannelFuture future) -> {
262             Throwable cause = future.cause();
263             if (cause != null) {
264                 promise.setFailure(cause);
265             } else {
266                 channel.connect(remoteaddress, localaddress).addListener(f -> {
267                     Throwable error = f.cause();
268                     if (error != null) {
269                         promise.setFailure(error);
270                     } else {
271                         promise.setSuccess(channel);
272                     }
273                 });
274             }
275         });
276         return promise;
277     }
278 }