View Javadoc
1   /*
2    * junixsocket
3    *
4    * Copyright 2009-2024 Christian Kohlschütter
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.newsclub.net.unix;
19  
20  import java.io.IOException;
21  import java.lang.reflect.InvocationTargetException;
22  import java.net.SocketAddress;
23  import java.net.SocketException;
24  import java.net.URI;
25  import java.nio.channels.ServerSocketChannel;
26  import java.nio.channels.SocketChannel;
27  import java.nio.channels.UnsupportedAddressTypeException;
28  import java.nio.channels.spi.SelectorProvider;
29  import java.util.Collections;
30  import java.util.HashMap;
31  import java.util.HashSet;
32  import java.util.Map;
33  import java.util.Objects;
34  import java.util.Set;
35  import java.util.concurrent.atomic.AtomicBoolean;
36  
37  import org.eclipse.jdt.annotation.NonNull;
38  import org.eclipse.jdt.annotation.Nullable;
39  import org.newsclub.net.unix.AFSocketAddress.AFSocketAddressConstructor;
40  
41  /**
42   * Describes an address family supported by junixsocket.
43   *
44   * @param <A> The corresponding {@link AFSocketAddress} subclass.
45   * @author Christian Kohlschütter
46   */
47  public final class AFAddressFamily<A extends AFSocketAddress> {
48    private static final Map<String, AFAddressFamily<?>> AF_MAP = Collections.synchronizedMap(
49        new HashMap<>());
50    private static final Map<String, AFAddressFamily<?>> URI_SCHEMES = Collections.synchronizedMap(
51        new HashMap<>());
52    private static final AtomicBoolean DEFERRED_INIT_DONE = new AtomicBoolean(false);
53  
54    private final int domain;
55    private AFSocketAddressConstructor<A> addressConstructor;
56    private @Nullable Class<A> addressClass;
57    private final String juxString;
58    private final String juxInetAddressSuffix;
59    private final String addressClassname;
60  
61    private String selectorProviderClassname;
62  
63    private AFSocket.Constructor<A> socketConstructor;
64    private AFServerSocket.Constructor<A> serverSocketConstructor;
65    private AFSocketAddressConfig<A> addressConfig;
66  
67    private SelectorProvider selectorProvider = null;
68  
69    static {
70      NativeUnixSocket.isLoaded(); // trigger init
71    }
72  
73    private AFAddressFamily(String juxString, int domain, String addressClassname) {
74      this.juxString = juxString;
75      this.domain = domain; // FIXME validate
76      this.addressClassname = addressClassname;
77      this.juxInetAddressSuffix = "." + juxString + AFInetAddress.INETADDR_SUFFIX;
78    }
79  
80    @SuppressWarnings("unchecked")
81    static synchronized <A extends AFSocketAddress> @NonNull AFAddressFamily<A> registerAddressFamily(
82        String juxString, int domain, String addressClassname) {
83      AFAddressFamily<?> af = AF_MAP.get(juxString);
84      if (af != null) {
85        if (af.getDomain() != domain) {
86          throw new IllegalStateException("Wrong domain for address family " + juxString + ": " + af
87              .getDomain() + " vs. " + domain);
88        }
89        return (AFAddressFamily<A>) af;
90      }
91  
92      af = new AFAddressFamily<>(juxString, domain, addressClassname);
93      AF_MAP.put(juxString, af);
94  
95      return (AFAddressFamily<A>) af;
96    }
97  
98    static synchronized void triggerInit() {
99      for (AFAddressFamily<?> af : new HashSet<>(AF_MAP.values())) {
100       if (af.addressClassname != null) {
101         try {
102           Class<?> clz = Class.forName(af.addressClassname);
103           clz.getMethod("addressFamily").invoke(null);
104         } catch (Exception e) {
105           // ignore
106         }
107       }
108     }
109   }
110 
111   static synchronized AFAddressFamily<?> getAddressFamily(String juxString) {
112     return AF_MAP.get(juxString);
113   }
114 
115   static AFAddressFamily<?> getAddressFamily(URI uri) {
116     checkDeferredInit();
117     Objects.requireNonNull(uri, "uri");
118     String scheme = uri.getScheme();
119     return URI_SCHEMES.get(scheme);
120   }
121 
122   static void checkDeferredInit() {
123     if (DEFERRED_INIT_DONE.compareAndSet(false, true)) {
124       NativeUnixSocket.isLoaded();
125       AFAddressFamily.triggerInit();
126     }
127   }
128 
129   int getDomain() {
130     return domain;
131   }
132 
133   String getJuxString() {
134     return juxString;
135   }
136 
137   AFSocketAddressConstructor<A> getAddressConstructor() {
138     if (addressConstructor == null) {
139       throw new UnsupportedAddressTypeException();
140     }
141     return addressConstructor;
142   }
143 
144   private synchronized void checkProvider() {
145     if (socketConstructor == null && selectorProvider == null) {
146       try {
147         getSelectorProvider();
148       } catch (IllegalStateException e) {
149         // ignore
150       }
151     }
152   }
153 
154   AFSocket.Constructor<A> getSocketConstructor() {
155     checkProvider();
156     if (socketConstructor == null) {
157       throw new UnsupportedAddressTypeException();
158     }
159     return socketConstructor;
160   }
161 
162   AFServerSocket.Constructor<A> getServerSocketConstructor() {
163     checkProvider();
164     if (serverSocketConstructor == null) {
165       throw new UnsupportedAddressTypeException();
166     }
167     return serverSocketConstructor;
168   }
169 
170   Class<A> getSocketAddressClass() {
171     if (addressClass == null) {
172       throw new UnsupportedAddressTypeException();
173     }
174     return addressClass;
175   }
176 
177   String getJuxInetAddressSuffix() {
178     return juxInetAddressSuffix;
179   }
180 
181   /**
182    * Registers an address family.
183    *
184    * @param <A> The supported address type.
185    * @param juxString The sockaddr_* identifier as registered in native code.
186    * @param addressClass The supported address subclass.
187    * @param config The address-specific config object.
188    * @return The corresponding {@link AFAddressFamily} instance.
189    */
190   @SuppressWarnings({"unchecked", "rawtypes"})
191   public static synchronized <A extends AFSocketAddress> AFAddressFamily<A> registerAddressFamily(
192       String juxString, //
193       Class<A> addressClass, AFSocketAddressConfig<A> config) {
194     AFAddressFamily<?> af = getAddressFamily(juxString);
195     if (af == null) {
196       throw new IllegalStateException("Address family not supported by native code: " + juxString);
197     }
198     if (af.addressClassname != null && !addressClass.getName().equals(af.addressClassname)) {
199       throw new IllegalStateException("Unexpected classname for address family " + juxString + ": "
200           + addressClass.getName() + "; expected: " + af.addressClassname);
201     }
202     if (af.addressConstructor != null || af.addressClass != null) {
203       throw new IllegalStateException("Already registered: " + juxString);
204     }
205     af.addressConfig = (AFSocketAddressConfig) config;
206     af.addressConstructor = (AFSocketAddressConstructor) config.addressConstructor();
207     af.addressClass = (Class) addressClass;
208     synchronized (af) { // work-around for likely false positive Spotbugs error
209       af.selectorProviderClassname = config.selectorProviderClassname();
210     }
211 
212     for (String scheme : config.uriSchemes()) {
213       if (scheme.isEmpty()) {
214         throw new IllegalStateException("Invalid URI scheme; cannot register " + scheme + " for "
215             + juxString);
216 
217       }
218       if (URI_SCHEMES.containsKey(scheme)) {
219         throw new IllegalStateException("URI scheme already registered; cannot register " + scheme
220             + " for " + juxString);
221       }
222       URI_SCHEMES.put(scheme, af);
223     }
224 
225     return (AFAddressFamily<A>) af;
226   }
227 
228   /**
229    * Registers an implementation.
230    *
231    * @param <A> The supported address type.
232    * @param juxString The sockaddr_* identifier as registered in native code.
233    * @param addressFamily The supported address family as registered via
234    *          {@link #registerAddressFamily(String, Class, AFSocketAddressConfig)}.
235    * @param config The address family-specific configuration object.
236    * @return The corresponding {@link AFAddressFamily} instance.
237    */
238   @SuppressWarnings({"unchecked", "rawtypes", "PMD.ExcessiveParameterList"})
239   public static synchronized <A extends AFSocketAddress> AFAddressFamily<A> registerAddressFamilyImpl(
240       String juxString, //
241       AFAddressFamily<A> addressFamily, //
242       AFAddressFamilyConfig<A> config) {
243     Objects.requireNonNull(addressFamily);
244     Objects.requireNonNull(config);
245 
246     AFAddressFamily<?> af = getAddressFamily(juxString);
247     if (af == null) {
248       throw new IllegalStateException("Unknown address family: " + juxString);
249     }
250     if (addressFamily != af) { // NOPMD.CompareObjectsWithEquals
251       throw new IllegalStateException("Address family inconsistency: " + juxString);
252     }
253     if (af.socketConstructor != null) {
254       throw new IllegalStateException("Already registered: " + juxString);
255     }
256     af.socketConstructor = (AFSocket.Constructor) config.socketConstructor();
257     af.serverSocketConstructor = (AFServerSocket.Constructor) config.serverSocketConstructor();
258 
259     FileDescriptorCast.registerCastingProviders(config);
260 
261     return (AFAddressFamily<A>) af;
262   }
263 
264   @SuppressWarnings("unchecked")
265   AFSocketImplExtensions<A> initImplExtensions(AncillaryDataSupport ancillaryDataSupport) {
266     switch (getDomain()) {
267       case NativeUnixSocket.DOMAIN_TIPC:
268         return (AFSocketImplExtensions<A>) new AFTIPCSocketImplExtensions(ancillaryDataSupport);
269       case NativeUnixSocket.DOMAIN_VSOCK:
270         return (AFSocketImplExtensions<A>) new AFVSOCKSocketImplExtensions(ancillaryDataSupport);
271       case NativeUnixSocket.DOMAIN_SYSTEM:
272         return (AFSocketImplExtensions<A>) new AFSYSTEMSocketImplExtensions(ancillaryDataSupport);
273       default:
274         throw new UnsupportedOperationException();
275     }
276   }
277 
278   /**
279    * Creates a new, unconnected, unbound socket compatible with this socket address.
280    *
281    * @return The socket instance.
282    * @throws IOException on error.
283    */
284   public AFSocket<?> newSocket() throws IOException {
285     try {
286       return getSocketConstructor().newInstance(null, null);
287     } catch (UnsupportedOperationException e) {
288       throw (SocketException) new SocketException().initCause(e);
289     }
290   }
291 
292   /**
293    * Creates a new, unconnected, unbound server socket compatible with this socket address.
294    *
295    * @return The server socket instance.
296    * @throws IOException on error.
297    */
298   public AFServerSocket<?> newServerSocket() throws IOException {
299     try {
300       return getServerSocketConstructor().newInstance(null);
301     } catch (UnsupportedOperationException e) {
302       throw (SocketException) new SocketException().initCause(e);
303     }
304   }
305 
306   /**
307    * Creates a new, unconnected, unbound {@link SocketChannel} compatible with this socket address.
308    *
309    * @return The socket instance.
310    * @throws IOException on error.
311    */
312   public AFSocketChannel<?> newSocketChannel() throws IOException {
313     return newSocket().getChannel();
314   }
315 
316   /**
317    * Creates a new, unconnected, unbound {@link ServerSocketChannel} compatible with this socket
318    * address.
319    *
320    * @return The socket instance.
321    * @throws IOException on error.
322    */
323   public AFServerSocketChannel<?> newServerSocketChannel() throws IOException {
324     return newServerSocket().getChannel();
325   }
326 
327   AFSocketAddress parseURI(URI u, int overridePort) throws SocketException {
328     if (addressConfig == null) {
329       throw new SocketException("Cannot instantiate addresses of type " + addressClass);
330     }
331     return addressConfig.parseURI(u, overridePort);
332   }
333 
334   /**
335    * Returns the set of supported URI schemes that can be parsed to some {@link AFSocketAddress}.
336    *
337    * The set is dependent on which {@link AFSocketAddress} implementations are registered with
338    * junixsocket.
339    *
340    * @return The set of supported URI schemes.
341    */
342   public static synchronized Set<String> uriSchemes() {
343     checkDeferredInit();
344     return Collections.unmodifiableSet(URI_SCHEMES.keySet());
345   }
346 
347   /**
348    * Returns the {@link SelectorProvider} associated with this address family, or {@code null} if no
349    * such instance is registered.
350    *
351    * @return The {@link SelectorProvider}.
352    * @throws IllegalStateException on error.
353    */
354   public synchronized SelectorProvider getSelectorProvider() {
355     if (selectorProvider != null) {
356       return selectorProvider;
357     }
358     if (selectorProviderClassname == null) {
359       return null;
360     }
361     try {
362       selectorProvider = (SelectorProvider) Class.forName(selectorProviderClassname).getMethod(
363           "provider", new Class<?>[0]).invoke(null);
364     } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException
365         | ClassNotFoundException | RuntimeException e) {
366       throw new IllegalStateException("Cannot instantiate selector provider for "
367           + addressClassname, e);
368     }
369     return selectorProvider;
370   }
371 
372   /**
373    * Returns an appropriate SocketAddress to be used when calling bind with a null argument.
374    *
375    * @return The new socket address, or {@code null}.
376    * @throws IOException on error.
377    */
378   public SocketAddress nullBindAddress() throws IOException {
379     return addressConfig.nullBindAddress();
380   }
381 }