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); + } + }