AFAddressFamily.java
/*
* junixsocket
*
* Copyright 2009-2024 Christian Kohlschütter
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.newsclub.net.unix;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.URI;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.UnsupportedAddressTypeException;
import java.nio.channels.spi.SelectorProvider;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.newsclub.net.unix.AFSocketAddress.AFSocketAddressConstructor;
/**
* Describes an address family supported by junixsocket.
*
* @param <A> The corresponding {@link AFSocketAddress} subclass.
* @author Christian Kohlschütter
*/
public final class AFAddressFamily<A extends AFSocketAddress> {
private static final Map<String, AFAddressFamily<?>> AF_MAP = Collections.synchronizedMap(
new HashMap<>());
private static final Map<String, AFAddressFamily<?>> URI_SCHEMES = Collections.synchronizedMap(
new HashMap<>());
private static final AtomicBoolean DEFERRED_INIT_DONE = new AtomicBoolean(false);
private final int domain;
private AFSocketAddressConstructor<A> addressConstructor;
private @Nullable Class<A> addressClass;
private final String juxString;
private final String juxInetAddressSuffix;
private final String addressClassname;
private String selectorProviderClassname;
private AFSocket.Constructor<A> socketConstructor;
private AFServerSocket.Constructor<A> serverSocketConstructor;
private AFSocketAddressConfig<A> addressConfig;
private SelectorProvider selectorProvider = null;
static {
NativeUnixSocket.isLoaded(); // trigger init
}
private AFAddressFamily(String juxString, int domain, String addressClassname) {
this.juxString = juxString;
this.domain = domain; // FIXME validate
this.addressClassname = addressClassname;
this.juxInetAddressSuffix = "." + juxString + AFInetAddress.INETADDR_SUFFIX;
}
@SuppressWarnings("unchecked")
static synchronized <A extends AFSocketAddress> @NonNull AFAddressFamily<A> registerAddressFamily(
String juxString, int domain, String addressClassname) {
AFAddressFamily<?> af = AF_MAP.get(juxString);
if (af != null) {
if (af.getDomain() != domain) {
throw new IllegalStateException("Wrong domain for address family " + juxString + ": " + af
.getDomain() + " vs. " + domain);
}
return (AFAddressFamily<A>) af;
}
af = new AFAddressFamily<>(juxString, domain, addressClassname);
AF_MAP.put(juxString, af);
return (AFAddressFamily<A>) af;
}
static synchronized void triggerInit() {
for (AFAddressFamily<?> af : new HashSet<>(AF_MAP.values())) {
if (af.addressClassname != null) {
try {
Class<?> clz = Class.forName(af.addressClassname);
clz.getMethod("addressFamily").invoke(null);
} catch (Exception e) {
// ignore
}
}
}
}
static synchronized AFAddressFamily<?> getAddressFamily(String juxString) {
return AF_MAP.get(juxString);
}
static AFAddressFamily<?> getAddressFamily(URI uri) {
checkDeferredInit();
Objects.requireNonNull(uri, "uri");
String scheme = uri.getScheme();
return URI_SCHEMES.get(scheme);
}
static void checkDeferredInit() {
if (DEFERRED_INIT_DONE.compareAndSet(false, true)) {
NativeUnixSocket.isLoaded();
AFAddressFamily.triggerInit();
}
}
int getDomain() {
return domain;
}
String getJuxString() {
return juxString;
}
AFSocketAddressConstructor<A> getAddressConstructor() {
if (addressConstructor == null) {
throw new UnsupportedAddressTypeException();
}
return addressConstructor;
}
private synchronized void checkProvider() {
if (socketConstructor == null && selectorProvider == null) {
try {
getSelectorProvider();
} catch (IllegalStateException e) {
// ignore
}
}
}
AFSocket.Constructor<A> getSocketConstructor() {
checkProvider();
if (socketConstructor == null) {
throw new UnsupportedAddressTypeException();
}
return socketConstructor;
}
AFServerSocket.Constructor<A> getServerSocketConstructor() {
checkProvider();
if (serverSocketConstructor == null) {
throw new UnsupportedAddressTypeException();
}
return serverSocketConstructor;
}
Class<A> getSocketAddressClass() {
if (addressClass == null) {
throw new UnsupportedAddressTypeException();
}
return addressClass;
}
String getJuxInetAddressSuffix() {
return juxInetAddressSuffix;
}
/**
* Registers an address family.
*
* @param <A> The supported address type.
* @param juxString The sockaddr_* identifier as registered in native code.
* @param addressClass The supported address subclass.
* @param config The address-specific config object.
* @return The corresponding {@link AFAddressFamily} instance.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public static synchronized <A extends AFSocketAddress> AFAddressFamily<A> registerAddressFamily(
String juxString, //
Class<A> addressClass, AFSocketAddressConfig<A> config) {
AFAddressFamily<?> af = getAddressFamily(juxString);
if (af == null) {
throw new IllegalStateException("Address family not supported by native code: " + juxString);
}
if (af.addressClassname != null && !addressClass.getName().equals(af.addressClassname)) {
throw new IllegalStateException("Unexpected classname for address family " + juxString + ": "
+ addressClass.getName() + "; expected: " + af.addressClassname);
}
if (af.addressConstructor != null || af.addressClass != null) {
throw new IllegalStateException("Already registered: " + juxString);
}
af.addressConfig = (AFSocketAddressConfig) config;
af.addressConstructor = (AFSocketAddressConstructor) config.addressConstructor();
af.addressClass = (Class) addressClass;
synchronized (af) { // work-around for likely false positive Spotbugs error
af.selectorProviderClassname = config.selectorProviderClassname();
}
for (String scheme : config.uriSchemes()) {
if (scheme.isEmpty()) {
throw new IllegalStateException("Invalid URI scheme; cannot register " + scheme + " for "
+ juxString);
}
if (URI_SCHEMES.containsKey(scheme)) {
throw new IllegalStateException("URI scheme already registered; cannot register " + scheme
+ " for " + juxString);
}
URI_SCHEMES.put(scheme, af);
}
return (AFAddressFamily<A>) af;
}
/**
* Registers an implementation.
*
* @param <A> The supported address type.
* @param juxString The sockaddr_* identifier as registered in native code.
* @param addressFamily The supported address family as registered via
* {@link #registerAddressFamily(String, Class, AFSocketAddressConfig)}.
* @param config The address family-specific configuration object.
* @return The corresponding {@link AFAddressFamily} instance.
*/
@SuppressWarnings({"unchecked", "rawtypes", "PMD.ExcessiveParameterList"})
public static synchronized <A extends AFSocketAddress> AFAddressFamily<A> registerAddressFamilyImpl(
String juxString, //
AFAddressFamily<A> addressFamily, //
AFAddressFamilyConfig<A> config) {
Objects.requireNonNull(addressFamily);
Objects.requireNonNull(config);
AFAddressFamily<?> af = getAddressFamily(juxString);
if (af == null) {
throw new IllegalStateException("Unknown address family: " + juxString);
}
if (addressFamily != af) { // NOPMD.CompareObjectsWithEquals
throw new IllegalStateException("Address family inconsistency: " + juxString);
}
if (af.socketConstructor != null) {
throw new IllegalStateException("Already registered: " + juxString);
}
af.socketConstructor = (AFSocket.Constructor) config.socketConstructor();
af.serverSocketConstructor = (AFServerSocket.Constructor) config.serverSocketConstructor();
FileDescriptorCast.registerCastingProviders(config);
return (AFAddressFamily<A>) af;
}
@SuppressWarnings("unchecked")
AFSocketImplExtensions<A> initImplExtensions(AncillaryDataSupport ancillaryDataSupport) {
switch (getDomain()) {
case NativeUnixSocket.DOMAIN_TIPC:
return (AFSocketImplExtensions<A>) new AFTIPCSocketImplExtensions(ancillaryDataSupport);
case NativeUnixSocket.DOMAIN_VSOCK:
return (AFSocketImplExtensions<A>) new AFVSOCKSocketImplExtensions(ancillaryDataSupport);
case NativeUnixSocket.DOMAIN_SYSTEM:
return (AFSocketImplExtensions<A>) new AFSYSTEMSocketImplExtensions(ancillaryDataSupport);
default:
throw new UnsupportedOperationException();
}
}
/**
* Creates a new, unconnected, unbound socket compatible with this socket address.
*
* @return The socket instance.
* @throws IOException on error.
*/
public AFSocket<?> newSocket() throws IOException {
try {
return getSocketConstructor().newInstance(null, null);
} catch (UnsupportedOperationException e) {
throw (SocketException) new SocketException().initCause(e);
}
}
/**
* Creates a new, unconnected, unbound server socket compatible with this socket address.
*
* @return The server socket instance.
* @throws IOException on error.
*/
public AFServerSocket<?> newServerSocket() throws IOException {
try {
return getServerSocketConstructor().newInstance(null);
} catch (UnsupportedOperationException e) {
throw (SocketException) new SocketException().initCause(e);
}
}
/**
* Creates a new, unconnected, unbound {@link SocketChannel} compatible with this socket address.
*
* @return The socket instance.
* @throws IOException on error.
*/
public AFSocketChannel<?> newSocketChannel() throws IOException {
return newSocket().getChannel();
}
/**
* Creates a new, unconnected, unbound {@link ServerSocketChannel} compatible with this socket
* address.
*
* @return The socket instance.
* @throws IOException on error.
*/
public AFServerSocketChannel<?> newServerSocketChannel() throws IOException {
return newServerSocket().getChannel();
}
AFSocketAddress parseURI(URI u, int overridePort) throws SocketException {
if (addressConfig == null) {
throw new SocketException("Cannot instantiate addresses of type " + addressClass);
}
return addressConfig.parseURI(u, overridePort);
}
/**
* Returns the set of supported URI schemes that can be parsed to some {@link AFSocketAddress}.
*
* The set is dependent on which {@link AFSocketAddress} implementations are registered with
* junixsocket.
*
* @return The set of supported URI schemes.
*/
public static synchronized Set<String> uriSchemes() {
checkDeferredInit();
return Collections.unmodifiableSet(URI_SCHEMES.keySet());
}
/**
* Returns the {@link SelectorProvider} associated with this address family, or {@code null} if no
* such instance is registered.
*
* @return The {@link SelectorProvider}.
* @throws IllegalStateException on error.
*/
public synchronized SelectorProvider getSelectorProvider() {
if (selectorProvider != null) {
return selectorProvider;
}
if (selectorProviderClassname == null) {
return null;
}
try {
selectorProvider = (SelectorProvider) Class.forName(selectorProviderClassname).getMethod(
"provider", new Class<?>[0]).invoke(null);
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException
| ClassNotFoundException | RuntimeException e) {
throw new IllegalStateException("Cannot instantiate selector provider for "
+ addressClassname, e);
}
return selectorProvider;
}
/**
* Returns an appropriate SocketAddress to be used when calling bind with a null argument.
*
* @return The new socket address, or {@code null}.
* @throws IOException on error.
*/
public SocketAddress nullBindAddress() throws IOException {
return addressConfig.nullBindAddress();
}
}