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 connectionAddress = QuicConnectionAddress.EPHEMERAL;
54      private ChannelHandler handler;
55      private ChannelHandler streamHandler;
56  
57      /**
58       * Creates a new instance which uses the given {@link Channel} to bootstrap the {@link QuicChannel}.
59       * This {@link io.netty.channel.ChannelPipeline} of the {@link Channel} needs to have the quic codec in the
60       * pipeline.
61       *
62       * @param parent    the {@link Channel} that is used as the transport layer.
63       */
64      QuicChannelBootstrap(Channel parent) {
65          Quic.ensureAvailability();
66          this.parent = ObjectUtil.checkNotNull(parent, "parent");
67      }
68  
69      /**
70       * Allow to specify a {@link ChannelOption} which is used for the {@link QuicChannel} instances once they got
71       * created. Use a value of {@code null} to remove a previous set {@link ChannelOption}.
72       *
73       * @param option    the {@link ChannelOption} to apply to the {@link QuicChannel}.
74       * @param value     the value of the option.
75       * @param <T>       the type of the value.
76       * @return          this instance.
77       */
78      public <T> QuicChannelBootstrap option(ChannelOption<T> option, @Nullable T value) {
79          Quic.updateOptions(options, option, value);
80          return this;
81      }
82  
83      /**
84       * Allow to specify an initial attribute of the newly created {@link QuicChannel}.  If the {@code value} is
85       * {@code null}, the attribute of the specified {@code key} is removed.
86       *
87       * @param key       the {@link AttributeKey} to apply to the {@link QuicChannel}.
88       * @param value     the value of the attribute.
89       * @param <T>       the type of the value.
90       * @return          this instance.
91       */
92      public <T> QuicChannelBootstrap attr(AttributeKey<T> key, @Nullable T value) {
93          Quic.updateAttributes(attrs, key, value);
94          return this;
95      }
96  
97      /**
98       * Set the {@link ChannelHandler} that is added to the {@link io.netty.channel.ChannelPipeline} of the
99       * {@link QuicChannel} once created.
100      *
101      * @param handler   the {@link ChannelHandler} that is added to the {@link QuicChannel}s
102      *                  {@link io.netty.channel.ChannelPipeline}.
103      * @return          this instance.
104      */
105     public QuicChannelBootstrap handler(ChannelHandler handler) {
106         this.handler = ObjectUtil.checkNotNull(handler, "handler");
107         return this;
108     }
109 
110     /**
111      * Allow to specify a {@link ChannelOption} which is used for the {@link QuicStreamChannel} instances once they got
112      * created. Use a value of {@code null} to remove a previous set {@link ChannelOption}.
113      *
114      * @param option    the {@link ChannelOption} to apply to the {@link QuicStreamChannel}s.
115      * @param value     the value of the option.
116      * @param <T>       the type of the value.
117      * @return          this instance.
118      */
119     public <T> QuicChannelBootstrap streamOption(ChannelOption<T> option, @Nullable T value) {
120         Quic.updateOptions(streamOptions, option, value);
121         return this;
122     }
123 
124     /**
125      * Allow to specify an initial attribute of the newly created {@link QuicStreamChannel}. If the {@code value} is
126      * {@code null}, the attribute of the specified {@code key} is removed.
127      *
128      * @param key       the {@link AttributeKey} to apply to the {@link QuicStreamChannel}s.
129      * @param value     the value of the attribute.
130      * @param <T>       the type of the value.
131      * @return          this instance.
132      */
133     public <T> QuicChannelBootstrap streamAttr(AttributeKey<T> key, @Nullable T value) {
134         Quic.updateAttributes(streamAttrs, key, value);
135         return this;
136     }
137 
138     /**
139      * Set the {@link ChannelHandler} that is added to the {@link io.netty.channel.ChannelPipeline} of the
140      * {@link QuicStreamChannel} once created.
141      *
142      * @param streamHandler     the {@link ChannelHandler} that is added to the {@link QuicStreamChannel}s
143      *                          {@link io.netty.channel.ChannelPipeline}.
144      * @return                  this instance.
145      */
146     public QuicChannelBootstrap streamHandler(ChannelHandler streamHandler) {
147         this.streamHandler = ObjectUtil.checkNotNull(streamHandler, "streamHandler");
148         return this;
149     }
150 
151     /**
152      * Set the local address.
153      *
154      * @param local    the {@link SocketAddress} of the local peer.
155      * @return          this instance.
156      */
157     public QuicChannelBootstrap localAddress(SocketAddress local) {
158         this.local = ObjectUtil.checkNotNull(local, "local");
159         return this;
160     }
161 
162     /**
163      * Set the remote address of the host to talk to.
164      *
165      * @param remote    the {@link SocketAddress} of the remote peer.
166      * @return          this instance.
167      */
168     public QuicChannelBootstrap remoteAddress(SocketAddress remote) {
169         this.remote = ObjectUtil.checkNotNull(remote, "remote");
170         return this;
171     }
172 
173     /**
174      * Set the {@link QuicConnectionAddress} to use. If none is specified a random address is generated on your
175      * behalf.
176      *
177      * @param connectionAddress     the {@link QuicConnectionAddress} to use.
178      * @return                      this instance.
179      */
180     public QuicChannelBootstrap connectionAddress(QuicConnectionAddress connectionAddress) {
181         this.connectionAddress = ObjectUtil.checkNotNull(connectionAddress, "connectionAddress");
182         return this;
183     }
184 
185     /**
186      * Connects a {@link QuicChannel} to the remote peer and notifies the future once done.
187      *
188      * @return {@link Future} which is notified once the operation completes.
189      */
190     public Future<QuicChannel> connect() {
191         return connect(parent.eventLoop().newPromise());
192     }
193 
194     /**
195      * Connects a {@link QuicChannel} to the remote peer and notifies the promise once done.
196      *
197      * @param promise   the {@link Promise} which is notified once the operations completes.
198      * @return          {@link Future} which is notified once the operation completes.
199 
200      */
201     public Future<QuicChannel> connect(Promise<QuicChannel> promise) {
202         if (handler == null && streamHandler == null) {
203             throw new IllegalStateException("handler and streamHandler not set");
204         }
205         SocketAddress local = this.local;
206         if (local == null) {
207             local = parent.localAddress();
208         }
209         if (local == null) {
210             local = new InetSocketAddress(0);
211         }
212 
213         SocketAddress remote = this.remote;
214         if (remote == null) {
215             remote = parent.remoteAddress();
216         }
217         if (remote == null) {
218             throw new IllegalStateException("remote not set");
219         }
220 
221         final QuicConnectionAddress address = connectionAddress;
222         QuicChannel channel = QuicheQuicChannel.forClient(parent, (InetSocketAddress)  local,
223                 (InetSocketAddress) remote,
224                 streamHandler, Quic.toOptionsArray(streamOptions), Quic.toAttributesArray(streamAttrs));
225 
226         Quic.setupChannel(channel, Quic.toOptionsArray(options), Quic.toAttributesArray(attrs), handler, logger);
227         EventLoop eventLoop = parent.eventLoop();
228         eventLoop.register(channel).addListener((ChannelFuture future) -> {
229             Throwable cause = future.cause();
230             if (cause != null) {
231                 promise.setFailure(cause);
232             } else {
233                 channel.connect(address).addListener(f -> {
234                     Throwable error = f.cause();
235                     if (error != null) {
236                         promise.setFailure(error);
237                     } else {
238                         promise.setSuccess(channel);
239                     }
240                 });
241             }
242         });
243         return promise;
244     }
245 }