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