diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFAddressFamily.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFAddressFamily.java
index 1380fe9c8..3df3e5f28 100644
--- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFAddressFamily.java
+++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFAddressFamily.java
@@ -19,6 +19,7 @@
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;
@@ -367,4 +368,14 @@ public synchronized SelectorProvider getSelectorProvider() {
}
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();
+ }
}
diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFServerSocket.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFServerSocket.java
index 32ca675f7..2b2ba2012 100644
--- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFServerSocket.java
+++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFServerSocket.java
@@ -240,6 +240,11 @@ public final AFServerSocket forceBindAddress(SocketAddress endpoint) {
});
}
+ @Override
+ public final void bind(SocketAddress endpoint) throws IOException {
+ bind(endpoint, 50);
+ }
+
@SuppressWarnings("unchecked")
@Override
public final void bind(SocketAddress endpoint, int backlog) throws IOException {
@@ -550,11 +555,6 @@ public final AFServerSocket bindHook(SocketAddressFilter hook) {
return this;
}
- @Override
- public void bind(SocketAddress endpoint) throws IOException {
- bind(endpoint, 50);
- }
-
@Override
public InetAddress getInetAddress() {
if (!isBound()) {
diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketAddressConfig.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketAddressConfig.java
index 48b949d7c..d71cc0a67 100644
--- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketAddressConfig.java
+++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketAddressConfig.java
@@ -17,6 +17,8 @@
*/
package org.newsclub.net.unix;
+import java.io.IOException;
+import java.net.SocketAddress;
import java.net.SocketException;
import java.net.URI;
import java.util.Set;
@@ -70,4 +72,14 @@ protected AFSocketAddressConfig() {
* @return The set of supported URI schemes.
*/
protected abstract Set uriSchemes();
+
+ /**
+ * 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.
+ */
+ protected SocketAddress nullBindAddress() throws IOException {
+ return null;
+ }
}
diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketImpl.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketImpl.java
index 7461e476c..bed10c9b8 100644
--- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketImpl.java
+++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFSocketImpl.java
@@ -386,7 +386,10 @@ protected final int available() throws IOException {
final void bind(SocketAddress addr, int options) throws IOException {
if (addr == null) {
- throw new IllegalArgumentException("Cannot bind to null address");
+ addr = addressFamily.nullBindAddress();
+ if (addr == null) {
+ throw new UnsupportedOperationException("Cannot bind to null address");
+ }
}
if (addr == AFSocketAddress.INTERNAL_DUMMY_BIND) { // NOPMD
diff --git a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXSocketAddress.java b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXSocketAddress.java
index 366d7ecde..8b1d073bf 100644
--- a/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXSocketAddress.java
+++ b/junixsocket-common/src/main/java/org/newsclub/net/unix/AFUNIXSocketAddress.java
@@ -81,6 +81,11 @@ protected String selectorProviderClassname() {
protected Set uriSchemes() {
return new HashSet<>(Arrays.asList("unix", "http+unix", "https+unix"));
}
+
+ @Override
+ protected SocketAddress nullBindAddress() throws IOException {
+ return AFUNIXSocketAddress.ofNewTempFile();
+ }
});
private AFUNIXSocketAddress(int port, final byte[] socketAddress, Lease nativeAddress)
diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/ServerSocketTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/ServerSocketTest.java
index e7ec12354..eab79ff03 100644
--- a/junixsocket-common/src/test/java/org/newsclub/net/unix/ServerSocketTest.java
+++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/ServerSocketTest.java
@@ -80,10 +80,12 @@ public void close() throws IOException {
public void testBindBadArguments() throws Exception {
try (ServerSocket sock = newServerSocket()) {
assertFalse(sock.isBound());
- assertThrows(IllegalArgumentException.class, () -> {
+
+ try {
sock.bind(null);
- });
- assertFalse(sock.isBound());
+ } catch (UnsupportedOperationException e) {
+ assertFalse(sock.isBound());
+ }
}
try (ServerSocket sock = newServerSocket()) {
assertFalse(sock.isBound());
diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/SocketChannelTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/SocketChannelTest.java
index 27c1d0beb..6e238f82e 100644
--- a/junixsocket-common/src/test/java/org/newsclub/net/unix/SocketChannelTest.java
+++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/SocketChannelTest.java
@@ -19,11 +19,13 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import java.io.IOException;
+import java.net.ServerSocket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.SocketTimeoutException;
@@ -432,4 +434,49 @@ public void testAcceptNotBoundYet() throws Exception {
ServerSocketChannel sc = newServerSocketChannel();
assertThrows(NotYetBoundException.class, sc::accept);
}
+
+ protected boolean mayTestBindNullThrowUnsupportedOperationException() {
+ return true;
+ }
+
+ protected boolean mayTestBindNullHaveNullLocalSocketAddress() {
+ return true;
+ }
+
+ protected void cleanupTestBindNull(ServerSocketChannel sc, SocketAddress addr) throws Exception {
+ }
+
+ protected ServerSocket socketIfPossible(ServerSocketChannel channel) {
+ try {
+ return channel.socket();
+ } catch (UnsupportedOperationException e) {
+ return null;
+ }
+ }
+
+ @Test
+ public void testBindNull() throws Exception {
+ try (ServerSocketChannel sc = newServerSocketChannel()) {
+ ServerSocket s = socketIfPossible(sc);
+ assertTrue(s == null || !s.isBound());
+ try {
+ sc.bind(null);
+ } catch (UnsupportedOperationException e) {
+ if (mayTestBindNullThrowUnsupportedOperationException()) {
+ // OK
+ return;
+ } else {
+ throw e;
+ }
+ }
+ assertTrue(s == null || s.isBound());
+
+ SocketAddress addr = sc.getLocalAddress();
+ if (!mayTestBindNullHaveNullLocalSocketAddress()) {
+ assertNotNull(addr);
+ }
+
+ cleanupTestBindNull(sc, addr);
+ }
+ }
}
diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/SocketChannelTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/SocketChannelTest.java
index d5797195d..506f30571 100644
--- a/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/SocketChannelTest.java
+++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/domain/SocketChannelTest.java
@@ -20,6 +20,7 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.net.ProtocolFamily;
+import java.net.SocketAddress;
import java.nio.channels.DatagramChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
@@ -75,4 +76,19 @@ public void testUnixDomainProtocolFamily() throws Exception {
assertEquals(AFUNIXDatagramChannel.class, ch.getClass());
}
}
+
+ @Override
+ protected boolean mayTestBindNullThrowUnsupportedOperationException() {
+ return false;
+ }
+
+ @Override
+ protected boolean mayTestBindNullHaveNullLocalSocketAddress() {
+ return false;
+ }
+
+ @Override
+ protected void cleanupTestBindNull(ServerSocketChannel sc, SocketAddress addr) throws Exception {
+ // nothing to do, as -- unlike JEP380 -- junixsocket cleans up its mess
+ }
}
diff --git a/junixsocket-common/src/test/java/org/newsclub/net/unix/jep380/SocketChannelTest.java b/junixsocket-common/src/test/java/org/newsclub/net/unix/jep380/SocketChannelTest.java
index 2e53d1c70..c8a3f7b51 100644
--- a/junixsocket-common/src/test/java/org/newsclub/net/unix/jep380/SocketChannelTest.java
+++ b/junixsocket-common/src/test/java/org/newsclub/net/unix/jep380/SocketChannelTest.java
@@ -17,14 +17,22 @@
*/
package org.newsclub.net.unix.jep380;
+import java.io.IOException;
import java.net.SocketAddress;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import org.newsclub.net.unix.AFSocketCapability;
import org.newsclub.net.unix.AFSocketCapabilityRequirement;
import com.kohlschutter.annotations.compiletime.SuppressFBWarnings;
+import com.kohlschutter.testutil.AvailabilityRequirement;
@AFSocketCapabilityRequirement(AFSocketCapability.CAPABILITY_UNIX_DOMAIN)
+@AvailabilityRequirement(classes = "java.net.UnixDomainSocketAddress", //
+ message = "This test requires Java 16 or later")
@SuppressFBWarnings("NM_SAME_SIMPLE_NAME_AS_SUPERCLASS")
public final class SocketChannelTest extends
org.newsclub.net.unix.SocketChannelTest {
@@ -32,4 +40,27 @@ public final class SocketChannelTest extends
public SocketChannelTest() {
super(JEP380AddressSpecifics.INSTANCE);
}
+
+ @Override
+ protected boolean mayTestBindNullThrowUnsupportedOperationException() {
+ return false;
+ }
+
+ @Override
+ protected boolean mayTestBindNullHaveNullLocalSocketAddress() {
+ return false;
+ }
+
+ @Override
+ protected void cleanupTestBindNull(ServerSocketChannel sc, SocketAddress addr)
+ throws ClassNotFoundException, IOException {
+ if (!Class.forName("java.net.UnixDomainSocketAddress").isAssignableFrom(addr.getClass())) {
+ return;
+ }
+
+ // JEP380 doesn't clean up socket files
+ Path p = Paths.get(addr.toString());
+ Files.delete(p);
+ }
+
}