diff --git a/src/one/nio/net/JavaDatagramSocket.java b/src/one/nio/net/JavaDatagramSocket.java index 5fe7700..78d19da 100644 --- a/src/one/nio/net/JavaDatagramSocket.java +++ b/src/one/nio/net/JavaDatagramSocket.java @@ -143,6 +143,16 @@ public final int write(ByteBuffer src) throws IOException { return ch.write(src); } + @Override + public int sendMsg(Msg msg, int flags) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public int recvMsg(Msg msg, int flags) throws IOException { + throw new UnsupportedOperationException(); + } + @Override public final void setBlocking(boolean blocking) { try { diff --git a/src/one/nio/net/JavaServerSocket.java b/src/one/nio/net/JavaServerSocket.java index ed72083..b6b32ce 100755 --- a/src/one/nio/net/JavaServerSocket.java +++ b/src/one/nio/net/JavaServerSocket.java @@ -122,6 +122,16 @@ public final int write(ByteBuffer src) throws IOException { throw new UnsupportedOperationException(); } + @Override + public int sendMsg(Msg msg, int flags) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public int recvMsg(Msg msg, int flags) throws IOException { + throw new UnsupportedOperationException(); + } + @Override public final void setBlocking(boolean blocking) { try { diff --git a/src/one/nio/net/JavaSocket.java b/src/one/nio/net/JavaSocket.java index 932b38b..55019ed 100755 --- a/src/one/nio/net/JavaSocket.java +++ b/src/one/nio/net/JavaSocket.java @@ -152,6 +152,16 @@ public final int write(ByteBuffer src) throws IOException { return ch.write(src); } + @Override + public int sendMsg(Msg msg, int flags) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public int recvMsg(Msg msg, int flags) throws IOException { + throw new UnsupportedOperationException(); + } + @Override public final void setBlocking(boolean blocking) { try { diff --git a/src/one/nio/net/Msg.java b/src/one/nio/net/Msg.java new file mode 100644 index 0000000..57873bd --- /dev/null +++ b/src/one/nio/net/Msg.java @@ -0,0 +1,79 @@ +/* + * Copyright 2021 Odnoklassniki Ltd, Mail.Ru Group + * + * 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 one.nio.net; + +import java.util.Arrays; + +public final class Msg { + public static final int SCM_RIGHTS = 1; + public static final int SCM_CREDENTIALS = 2; + + private byte[] data; + private int cmsgType; + private int[] cmsgData; + + public Msg(int capacity) { + this.data = new byte[capacity]; + } + + public Msg(byte[] data) { + this.data = data; + } + + public byte[] data() { + return data; + } + + public int cmsgType() { + return cmsgType; + } + + public int[] cmsgData() { + return cmsgData; + } + + public Msg withCmsg(int type, int... data) { + if (cmsgType != 0) { + throw new IllegalStateException("cmsg already set"); + } + cmsgType = type; + cmsgData = data; + return this; + } + + public Msg withFd(int fd) { + return withCmsg(SCM_RIGHTS, fd); + } + + public Msg withFd(int... fds) { + return withCmsg(SCM_RIGHTS, fds); + } + + public Msg withCred(int pid, int uid, int gid) { + return withCmsg(SCM_CREDENTIALS, pid, uid, gid); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("Msg(").append(data == null ? 0 : data.length); + if (cmsgType != 0) { + sb.append(cmsgType == SCM_RIGHTS ? " + SCM_RIGHTS" : " + SCM_CREDENTIALS"); + sb.append(Arrays.toString(cmsgData)); + } + return sb.append(')').toString(); + } +} diff --git a/src/one/nio/net/NativeSocket.java b/src/one/nio/net/NativeSocket.java index f0d922a..80a6573 100755 --- a/src/one/nio/net/NativeSocket.java +++ b/src/one/nio/net/NativeSocket.java @@ -22,7 +22,6 @@ import java.io.IOException; import java.io.RandomAccessFile; -import java.lang.reflect.Field; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; @@ -31,22 +30,13 @@ class NativeSocket extends Socket { private static final int INET_FAMILY = initNatives(Boolean.getBoolean("java.net.preferIPv4Stack")); - private static final Field ARRAY_FIELD; - private static final Field OFFSET_FIELD; - - static { - ARRAY_FIELD = JavaInternals.getField(ByteBuffer.class, "hb"); - OFFSET_FIELD = JavaInternals.getField(ByteBuffer.class, "offset"); - } + private static final long ARRAY_FIELD = JavaInternals.fieldOffset(ByteBuffer.class, "hb"); + private static final long OFFSET_FIELD = JavaInternals.fieldOffset(ByteBuffer.class, "offset"); int fd; - NativeSocket(int domain, boolean datagram) throws IOException { - this.fd = socket0(domain, datagram); - } - - NativeSocket(boolean datagram) throws IOException { - this.fd = socket0(INET_FAMILY, datagram); + NativeSocket(int domain, int type) throws IOException { + this.fd = socket0(domain != 0 ? domain : INET_FAMILY, type); } NativeSocket(int fd) { @@ -157,11 +147,11 @@ private int sendTo(ByteBuffer src, int flags, Object address, int port) throws I } private byte[] getArray(ByteBuffer src) throws IllegalAccessException { - return (byte[]) ARRAY_FIELD.get(src); + return (byte[]) JavaInternals.unsafe.getObject(src, ARRAY_FIELD); } private int getOffset(ByteBuffer src) throws IllegalAccessException { - return OFFSET_FIELD.getInt(src); + return JavaInternals.unsafe.getInt(src, OFFSET_FIELD); } @Override @@ -235,6 +225,16 @@ public int write(ByteBuffer src) throws IOException { return bytes; } + @Override + public int sendMsg(Msg msg, int flags) throws IOException { + return sendMsg0(msg.data(), msg.cmsgType(), msg.cmsgData(), flags); + } + + @Override + public int recvMsg(Msg msg, int flags) throws IOException { + return recvMsg0(msg.data(), msg, flags); + } + @Override public final native void setBlocking(boolean blocking); @@ -310,7 +310,7 @@ static Object toNativeAddr(String host, int port) throws UnknownHostException { private static native int initNatives(boolean preferIPv4); - private static native int socket0(int domain, boolean datagram) throws IOException; + private static native int socket0(int domain, int type) throws IOException; final native void connect0(Object address, int port) throws IOException; final native void bind0(Object address, int port) throws IOException; @@ -320,4 +320,6 @@ static Object toNativeAddr(String host, int port) throws UnknownHostException { final native int sendTo1(long buf, int size, int flags, Object address, int port) throws IOException; final native int recvFrom0(byte[] data, int offset, int maxSize, int flags, AddressHolder holder) throws IOException; final native int recvFrom1(long buf, int maxSize, int flags, AddressHolder holder) throws IOException; + final native int sendMsg0(byte[] data, int cmsgType, int[] cmsgData, int flags) throws IOException; + final native int recvMsg0(byte[] data, Msg msg, int flags) throws IOException; } diff --git a/src/one/nio/net/Socket.java b/src/one/nio/net/Socket.java index a2e2653..df5b6ca 100755 --- a/src/one/nio/net/Socket.java +++ b/src/one/nio/net/Socket.java @@ -32,6 +32,13 @@ public abstract class Socket implements ByteChannel { public static final int AF_INET = 2; public static final int AF_INET6 = 10; + // Socket types + public static final int SOCK_STREAM = 1; + public static final int SOCK_DGRAM = 2; + public static final int SOCK_RAW = 3; + public static final int SOCK_RDM = 4; + public static final int SOCK_SEQPACKET = 5; + // Use when the address has no port (i.e. for AF_UNIX address) public static final int NO_PORT = -1; @@ -57,6 +64,29 @@ public abstract class Socket implements ByteChannel { public static final int IPTOS_THROUGHPUT = 0x08; public static final int IPTOS_LOWDELAY = 0x10; + // Socket options + public static final int SO_DEBUG = 1; + public static final int SO_REUSEADDR = 2; + public static final int SO_TYPE = 3; + public static final int SO_ERROR = 4; + public static final int SO_DONTROUTE = 5; + public static final int SO_BROADCAST = 6; + public static final int SO_SNDBUF = 7; + public static final int SO_RCVBUF = 8; + public static final int SO_KEEPALIVE = 9; + public static final int SO_OOBINLINE = 10; + public static final int SO_NO_CHECK = 11; + public static final int SO_PRIORITY = 12; + public static final int SO_LINGER = 13; + public static final int SO_BSDCOMPAT = 14; + public static final int SO_REUSEPORT = 15; + public static final int SO_PASSCRED = 16; + public static final int SO_PEERCRED = 17; + public static final int SO_RCVLOWAT = 18; + public static final int SO_SNDLOWAT = 19; + public static final int SO_RCVTIMEO = 20; + public static final int SO_SNDTIMEO = 21; + // TCP socket options public static final int TCP_NODELAY = 1; public static final int TCP_MAXSEG = 2; @@ -88,6 +118,8 @@ public abstract class Socket implements ByteChannel { public abstract void readFully(byte[] data, int offset, int count) throws IOException; public abstract InetSocketAddress recv(ByteBuffer dst, int flags) throws IOException; public abstract long sendFile(RandomAccessFile file, long offset, long count) throws IOException; + public abstract int sendMsg(Msg msg, int flags) throws IOException; + public abstract int recvMsg(Msg msg, int flags) throws IOException; public abstract void setBlocking(boolean blocking); public abstract boolean isBlocking(); public abstract void setTimeout(int timeout); @@ -149,23 +181,23 @@ public int read(byte[] data, int offset, int count) throws IOException { } public static Socket create() throws IOException { - return NativeLibrary.IS_SUPPORTED ? new NativeSocket(false) : new JavaSocket(); + return NativeLibrary.IS_SUPPORTED ? new NativeSocket(0, SOCK_STREAM) : new JavaSocket(); } public static Socket createServerSocket() throws IOException { - return NativeLibrary.IS_SUPPORTED ? new NativeSocket(false) : new JavaServerSocket(); + return NativeLibrary.IS_SUPPORTED ? new NativeSocket(0, SOCK_STREAM) : new JavaServerSocket(); } public static Socket createDatagramSocket() throws IOException { - return NativeLibrary.IS_SUPPORTED ? new NativeSocket(true) : new JavaDatagramSocket(); + return NativeLibrary.IS_SUPPORTED ? new NativeSocket(0, SOCK_DGRAM) : new JavaDatagramSocket(); } - public static Socket createUnixSocket() throws IOException { + public static Socket createUnixSocket(int type) throws IOException { if (!NativeLibrary.IS_SUPPORTED) { throw new IOException("Unix sockets are supported in native mode only"); } - return new NativeSocket(AF_UNIX, true); + return new NativeSocket(AF_UNIX, type); } public static Socket connectInet(InetAddress address, int port) throws IOException { @@ -186,7 +218,7 @@ public static Socket connectUnix(File unixPath) throws IOException { throw new IOException("Unix sockets are supported in native mode only"); } - NativeSocket sock = new NativeSocket(AF_UNIX, false); + NativeSocket sock = new NativeSocket(AF_UNIX, SOCK_STREAM); sock.connect(unixPath.getAbsolutePath(), NO_PORT); return sock; } @@ -196,7 +228,7 @@ public static Socket bindUnix(File unixPath, int backlog) throws IOException { throw new IOException("Unix sockets are supported in native mode only"); } - NativeSocket sock = new NativeSocket(AF_UNIX, false); + NativeSocket sock = new NativeSocket(AF_UNIX, SOCK_STREAM); sock.bind(unixPath.getAbsolutePath(), NO_PORT, backlog); sock.listen(backlog); return sock; diff --git a/src/one/nio/net/native/jni_util.h b/src/one/nio/net/native/jni_util.h index aed15da..fe5e794 100755 --- a/src/one/nio/net/native/jni_util.h +++ b/src/one/nio/net/native/jni_util.h @@ -14,6 +14,8 @@ * limitations under the License. */ +#pragma once + #define MAX_STACK_BUF 65536 #define SIG_WAKEUP (__SIGRTMAX - 2) diff --git a/src/one/nio/net/native/socket.c b/src/one/nio/net/native/socket.c index 3aae24f..6576f94 100755 --- a/src/one/nio/net/native/socket.c +++ b/src/one/nio/net/native/socket.c @@ -35,6 +35,8 @@ static int use_IPv6 = 0; static jfieldID f_fd; +static jfieldID f_cmsgType; +static jfieldID f_cmsgData; static jclass c_AddressHolder; static jfieldID f_address; static jmethodID m_createIPv4Address; @@ -194,6 +196,10 @@ Java_one_nio_net_NativeSocket_initNatives(JNIEnv* env, jclass cls, jboolean pref // Cache field ID to access NativeSocket.fd f_fd = cache_field(env, "one/nio/net/NativeSocket", "fd", "I"); + // Cache Msg fields + f_cmsgType = cache_field(env, "one/nio/net/Msg", "cmsgType", "I"); + f_cmsgData = cache_field(env, "one/nio/net/Msg", "cmsgData", "[I"); + // Cache method IDs to produce InetSocketAddress c_AddressHolder = (*env)->NewGlobalRef(env, (*env)->FindClass(env, "one/nio/net/AddressHolder")); f_address = (*env)->GetFieldID(env, c_AddressHolder, "address", "Ljava/net/InetSocketAddress;"); @@ -215,8 +221,8 @@ Java_one_nio_net_NativeSocket_initNatives(JNIEnv* env, jclass cls, jboolean pref } JNIEXPORT jint JNICALL -Java_one_nio_net_NativeSocket_socket0(JNIEnv* env, jclass cls, jint domain, jboolean datagram) { - int result = socket(domain, datagram ? SOCK_DGRAM : SOCK_STREAM, 0); +Java_one_nio_net_NativeSocket_socket0(JNIEnv* env, jclass cls, jint domain, jint type) { + int result = socket(domain, type, 0); if (result == -1) { throw_io_exception(env); } @@ -542,6 +548,119 @@ Java_one_nio_net_NativeSocket_recvFrom1(JNIEnv* env, jobject self, jlong buffer, return 0; } +JNIEXPORT jint JNICALL +Java_one_nio_net_NativeSocket_sendMsg0(JNIEnv* env, jobject self, jbyteArray data, + jint cmsgType, jintArray cmsgData, jint flags) { + int fd = (*env)->GetIntField(env, self, f_fd); + jbyte data_buf[MAX_STACK_BUF - 4096]; + struct { + struct cmsghdr hdr; + jint data[1000]; + } cmsg_buf; + + if (fd == -1) { + throw_socket_closed(env); + } else { + struct msghdr msg = {0}; + struct iovec iov; + + if (data != NULL) { + jint data_len = (*env)->GetArrayLength(env, data); + if (data_len > sizeof(data_buf)) { + data_len = sizeof(data_buf); + } + (*env)->GetByteArrayRegion(env, data, 0, data_len, data_buf); + + iov.iov_base = data_buf; + iov.iov_len = data_len; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + } + + if (cmsgData != NULL) { + jint cmsgLen = (*env)->GetArrayLength(env, cmsgData); + if (cmsgLen > sizeof(cmsg_buf.data) / sizeof(jint)) { + cmsgLen = sizeof(cmsg_buf.data) / sizeof(jint); + } + (*env)->GetIntArrayRegion(env, cmsgData, 0, cmsgLen, cmsg_buf.data); + + cmsg_buf.hdr.cmsg_level = SOL_SOCKET; + cmsg_buf.hdr.cmsg_type = cmsgType; + cmsg_buf.hdr.cmsg_len = CMSG_LEN(cmsgLen * sizeof(jint)); + msg.msg_control = &cmsg_buf; + msg.msg_controllen = CMSG_SPACE(cmsgLen * sizeof(jint)); + } + + ssize_t result = sendmsg(fd, &msg, flags); + + if (result >= 0) { + return result; + } else if (is_io_exception(fd)) { + throw_io_exception(env); + } + } + return 0; +} + +JNIEXPORT jint JNICALL +Java_one_nio_net_NativeSocket_recvMsg0(JNIEnv* env, jobject self, jbyteArray data, + jobject jmsg, jint flags) { + int fd = (*env)->GetIntField(env, self, f_fd); + jbyte data_buf[MAX_STACK_BUF - 4096]; + struct { + struct cmsghdr hdr; + jint data[1000]; + } cmsg_buf; + + if (fd == -1) { + throw_socket_closed(env); + } else { + struct msghdr msg = {0}; + struct iovec iov; + + if (data != NULL) { + int data_len = (*env)->GetArrayLength(env, data); + if (data_len > sizeof(data_buf)) { + data_len = sizeof(data_buf); + } + + iov.iov_base = data_buf; + iov.iov_len = data_len; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + } + + if (jmsg != NULL) { + msg.msg_control = &cmsg_buf; + msg.msg_controllen = sizeof(cmsg_buf); + } + + ssize_t result = recvmsg(fd, &msg, flags); + + if (result >= 0) { + if (data != NULL) { + (*env)->SetByteArrayRegion(env, data, 0, result, data_buf); + } + + struct cmsghdr* hdr = CMSG_FIRSTHDR(&msg); + if (jmsg != NULL && hdr != NULL && hdr->cmsg_level == SOL_SOCKET) { + jint cmsgLen = (hdr->cmsg_len - CMSG_LEN(0)) / sizeof(jint); + jintArray cmsgData = (*env)->NewIntArray(env, cmsgLen); + if (cmsgData != NULL) { + (*env)->SetIntArrayRegion(env, cmsgData, 0, cmsgLen, cmsg_buf.data); + (*env)->SetIntField(env, jmsg, f_cmsgType, hdr->cmsg_type); + (*env)->SetObjectField(env, jmsg, f_cmsgData, cmsgData); + } + } + + return result; + } else if (is_io_exception(fd)) { + throw_io_exception(env); + } + } + return 0; +} + JNIEXPORT jobject JNICALL Java_one_nio_net_NativeSocket_getLocalAddress(JNIEnv* env, jobject self) { int fd = (*env)->GetIntField(env, self, f_fd); diff --git a/src/one/nio/net/native/sslcompat.h b/src/one/nio/net/native/sslcompat.h index 15e546d..2b7596d 100644 --- a/src/one/nio/net/native/sslcompat.h +++ b/src/one/nio/net/native/sslcompat.h @@ -16,6 +16,8 @@ // Compatibility layer between OpenSSL 1.0.x and 1.1.0 +#pragma once + #if OPENSSL_VERSION_NUMBER < 0x10100000L // The following symbols will be overridden if linked against libssl.so.11 diff --git a/src/one/nio/os/systemd/SystemdNotify.java b/src/one/nio/os/systemd/SystemdNotify.java index ca128d5..2694c34 100644 --- a/src/one/nio/os/systemd/SystemdNotify.java +++ b/src/one/nio/os/systemd/SystemdNotify.java @@ -54,7 +54,7 @@ public static void notify(String state) throws IOException { return; } - try (Socket socket = Socket.createUnixSocket()) { + try (Socket socket = Socket.createUnixSocket(Socket.SOCK_DGRAM)) { if (log.isDebugEnabled()) { log.debug(String.format("send '%s' to notify socket '%s'", state, notifySocket)); } diff --git a/test/one/nio/net/PerfServerTest.java b/test/one/nio/net/PerfServerTest.java new file mode 100644 index 0000000..d8a1ac9 --- /dev/null +++ b/test/one/nio/net/PerfServerTest.java @@ -0,0 +1,72 @@ +/* + * Copyright 2021 Odnoklassniki Ltd, Mail.Ru Group + * + * 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 one.nio.net; + +import one.nio.mem.DirectMemory; +import one.nio.os.Mem; + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; + +public class PerfServerTest { + + private static ByteBuffer wrap(byte[] buf) { + return ByteBuffer.wrap(buf).order(ByteOrder.nativeOrder()); + } + + public static void main(String[] args) throws Exception { + String sockPath = args.length > 0 ? args[0] : "/tmp/one-nio.sock"; + String mapPath = args.length > 1 ? args[1] : "/tmp/one-nio-map.tmp"; + long mapSize = args.length > 2 ? Long.parseLong(args[2]) : 16 * 1024 * 1024; + + RandomAccessFile raf = new RandomAccessFile(mapPath, "rw"); + raf.setLength(mapSize); + int fd = Mem.getFD(raf.getFD()); + + MappedByteBuffer map = raf.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, mapSize); + long mapAddr = DirectMemory.getAddress(map); + + Socket srv = Socket.bindUnix(new File(sockPath), 1); + byte[] request = new byte[4]; + byte[] response = new byte[20]; + + while (true) { + Socket s = srv.accept(); + byte[] cred = s.getOption(Socket.SOL_SOCKET, Socket.SO_PEERCRED); + if (cred == null) { + throw new IOException("Cannot get peer pid"); + } + int clientPid = wrap(cred).getInt(0); + + int bytesRead = s.read(request, 0, request.length); + if (bytesRead != 4 || wrap(request).getInt(0) != 2) { + throw new IOException("Invalid request"); + } + + ByteBuffer buf = wrap(response); + buf.putInt(2).putInt(0).putLong(mapSize).putInt(0); + + s.sendMsg(new Msg(response).withFd(fd), 0); + s.close(); + } + } +} diff --git a/test/one/nio/net/SocketTest.java b/test/one/nio/net/SocketTest.java index fe65403..a73ba0f 100755 --- a/test/one/nio/net/SocketTest.java +++ b/test/one/nio/net/SocketTest.java @@ -56,7 +56,7 @@ public static void main(String[] args) throws Exception { @Test public void testNativeSocketOpts() throws IOException { if (NativeLibrary.IS_SUPPORTED) { - SocketTest.testSocketOpts(new NativeSocket(false), false); + SocketTest.testSocketOpts(new NativeSocket(0, Socket.SOCK_STREAM), false); } } diff --git a/test/one/nio/net/UnixSocketTest.java b/test/one/nio/net/UnixSocketTest.java new file mode 100644 index 0000000..f778210 --- /dev/null +++ b/test/one/nio/net/UnixSocketTest.java @@ -0,0 +1,74 @@ +/* + * Copyright 2021 Odnoklassniki Ltd, Mail.Ru Group + * + * 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 one.nio.net; + +import one.nio.os.Proc; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import static org.junit.Assert.*; + +public class UnixSocketTest { + + @Test + public void testSendDescriptors() throws IOException { + File sockpath = new File("/tmp/one-nio-test.sock"); + sockpath.delete(); + + Socket server = Socket.bindUnix(sockpath, 1); + new Thread(() -> acceptorThread(server)).start(); + + Socket client = Socket.connectUnix(sockpath); + Msg msg = new Msg("hello".getBytes()).withFd(2, 1, 0); + + int bytes = client.sendMsg(msg, 0); + assertEquals(5, bytes); + + client.close(); + } + + private void acceptorThread(Socket server) { + try { + Socket s = server.accept(); + + byte[] cred = s.getOption(Socket.SOL_SOCKET, Socket.SO_PEERCRED); + int clientPid = ByteBuffer.wrap(cred, 0, 4).order(ByteOrder.nativeOrder()).getInt(); + assertEquals(Proc.getpid(), clientPid); + + Msg msg = new Msg(100); + int bytes = s.recvMsg(msg, 0); + + assertEquals(5, bytes); + assertEquals("hello", new String(msg.data(), 0, 5)); + assertEquals(Msg.SCM_RIGHTS, msg.cmsgType()); + assertEquals(3, msg.cmsgData().length); + + s.close(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public static void main(String[] args) throws Exception { + new UnixSocketTest().testSendDescriptors(); + } +}