AFSocketImpl.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 static org.newsclub.net.unix.NativeUnixSocket.SHUT_RD;
import static org.newsclub.net.unix.NativeUnixSocket.SHUT_RD_WR;
import static org.newsclub.net.unix.NativeUnixSocket.SHUT_WR;
import java.io.EOFException;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.SocketImpl;
import java.net.SocketOption;
import java.net.SocketOptions;
import java.net.SocketTimeoutException;
import java.nio.ByteBuffer;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
/**
* junixsocket-based {@link SocketImpl}.
*
* @author Christian Kohlschütter
* @param <A> The supported address type.
*/
@SuppressWarnings({
"PMD.CyclomaticComplexity", "PMD.CouplingBetweenObjects",
"UnsafeFinalization" /* errorprone */})
public abstract class AFSocketImpl<A extends AFSocketAddress> extends SocketImplShim {
private static final int SHUTDOWN_RD_WR = (1 << SHUT_RD) | (1 << SHUT_WR);
private final AFSocketStreamCore core;
final AncillaryDataSupport ancillaryDataSupport = new AncillaryDataSupport();
private final AtomicBoolean bound = new AtomicBoolean(false);
private Boolean createType = null;
private final AtomicBoolean connected = new AtomicBoolean(false);
private volatile boolean closedInputStream = false;
private volatile boolean closedOutputStream = false;
private final AFInputStream in;
private final AFOutputStream out;
private boolean reuseAddr = true;
private final AtomicInteger socketTimeout = new AtomicInteger(0);
private final AFAddressFamily<A> addressFamily;
private int shutdownState = 0;
private AFSocketImplExtensions<A> implExtensions = null;
/**
* When the {@link AFSocketImpl} becomes unreachable (but not yet closed), we must ensure that the
* underlying socket and all related file descriptors are closed.
*
* @author Christian Kohlschütter
*/
static final class AFSocketStreamCore extends AFSocketCore {
AFSocketStreamCore(AFSocketImpl<?> observed, FileDescriptor fd,
AncillaryDataSupport ancillaryDataSupport, AFAddressFamily<?> af) {
super(observed, fd, ancillaryDataSupport, af, false);
}
void createSocket(FileDescriptor fdTarget, AFSocketType type) throws IOException {
NativeUnixSocket.createSocket(fdTarget, addressFamily().getDomain(), type.getId());
}
/**
* Unblock other threads that are currently waiting on accept, simply by connecting to the
* socket.
*/
@Override
protected void unblockAccepts() {
if (socketAddress == null || socketAddress.getBytes() == null || inode.get() < 0) {
return;
}
while (hasPendingAccepts()) {
try {
FileDescriptor tmpFd = new FileDescriptor();
try {
createSocket(tmpFd, AFSocketType.SOCK_STREAM);
ByteBuffer ab = socketAddress.getNativeAddressDirectBuffer();
NativeUnixSocket.connect(ab, ab.limit(), tmpFd, inode.get());
} catch (IOException e) {
// there's nothing more we can do to unlock these accepts
// (e.g., SocketException: No such file or directory)
return;
}
if (isShutdownOnClose()) {
try {
NativeUnixSocket.shutdown(tmpFd, SHUT_RD_WR);
} catch (Exception e) {
// ignore
}
}
try {
NativeUnixSocket.close(tmpFd);
} catch (Exception e) {
// ignore
}
} catch (RuntimeException e) {
// ignore
}
// sleep a little to give the cleaners some CPU time to actually clean up
try {
Thread.sleep(5);
} catch (InterruptedException e) {
// ignore
}
}
}
}
/**
* Creates a new {@link AFSocketImpl} instance.
*
* @param addressFamily The address family.
* @param fdObj The socket's {@link FileDescriptor}.
*/
protected AFSocketImpl(AFAddressFamily<@NonNull A> addressFamily, FileDescriptor fdObj) {
super();
this.addressFamily = addressFamily;
this.address = InetAddress.getLoopbackAddress();
this.core = new AFSocketStreamCore(this, fdObj, ancillaryDataSupport, addressFamily);
this.fd = core.fd;
this.in = newInputStream();
this.out = newOutputStream();
}
/**
* Creates a new {@link InputStream} for this socket.
*
* @return The new stream.
*/
protected final AFInputStream newInputStream() {
return new AFInputStreamImpl();
}
/**
* Creates a new {@link OutputStream} for this socket.
*
* @return The new stream.
*/
protected final AFOutputStream newOutputStream() {
return new AFOutputStreamImpl();
}
final FileDescriptor getFD() {
return fd;
}
// CPD-OFF
final boolean isConnected() {
if (connected.get()) {
return true;
}
if (isClosed()) {
return false;
}
if (core.isConnected(false)) {
connected.set(true);
return true;
}
return false;
}
final boolean isBound() {
if (bound.get()) {
return true;
}
if (isClosed()) {
return false;
}
if (core.isConnected(true)) {
bound.set(true);
return true;
}
return false;
}
final AFSocketCore getCore() {
return core;
}
boolean isClosed() {
return core.isClosed();
}
// CPD-ON
@Override
protected final void accept(SocketImpl socket) throws IOException {
accept0(socket);
}
@SuppressWarnings("Finally" /* errorprone */)
final boolean accept0(SocketImpl socket) throws IOException {
FileDescriptor fdesc = core.validFdOrException();
if (isClosed()) {
throw new SocketException("Socket is closed");
} else if (!isBound()) {
throw new SocketException("Socket is not bound");
}
AFSocketAddress socketAddress = core.socketAddress;
AFSocketAddress boundSocketAddress = getLocalSocketAddress();
if (boundSocketAddress != null) {
// Always resolve bound address from wildcard address, etc.
core.socketAddress = socketAddress = boundSocketAddress;
}
if (socketAddress == null) {
throw new SocketException("Socket is not bound");
}
@SuppressWarnings("unchecked")
final AFSocketImpl<A> si = (AFSocketImpl<A>) socket;
core.incPendingAccepts();
try {
ByteBuffer ab = socketAddress.getNativeAddressDirectBuffer();
SocketException caught = null;
try {
if (!NativeUnixSocket.accept(ab, ab.limit(), fdesc, si.fd, core.inode.get(), socketTimeout
.get())) {
return false;
}
} catch (SocketException e) { // NOPMD.ExceptionAsFlowControl
caught = e;
} finally { // NOPMD.DoNotThrowExceptionInFinally
if (!isBound() || isClosed()) {
if (getCore().isShutdownOnClose()) {
try {
NativeUnixSocket.shutdown(si.fd, SHUT_RD_WR);
} catch (Exception e) {
// ignore
}
}
try {
NativeUnixSocket.close(si.fd);
} catch (Exception e) {
// ignore
}
if (caught != null) {
throw caught;
} else {
throw new SocketClosedException("Socket is closed");
}
} else if (caught != null) {
throw caught;
}
}
} finally {
core.decPendingAccepts();
}
si.setSocketAddress(socketAddress);
si.connected.set(true);
return true;
}
final void setSocketAddress(AFSocketAddress socketAddress) {
if (socketAddress == null) {
this.core.socketAddress = null;
this.address = null;
this.localport = -1;
} else {
this.core.socketAddress = socketAddress;
this.address = socketAddress.getAddress();
if (this.localport <= 0) {
this.localport = socketAddress.getPort();
}
}
}
@Override
protected final int available() throws IOException {
FileDescriptor fdesc = core.validFdOrException();
return NativeUnixSocket.available(fdesc, core.getThreadLocalDirectByteBuffer(0));
}
final void bind(SocketAddress addr, int options) throws IOException {
if (addr == null) {
throw new IllegalArgumentException("Cannot bind to null address");
}
if (!(addr instanceof AFSocketAddress)) {
throw new SocketException("Cannot bind to this type of address: " + addr.getClass());
}
bound.set(true);
if (addr == AFSocketAddress.INTERNAL_DUMMY_BIND) { // NOPMD
core.inode.set(0);
return;
}
AFSocketAddress socketAddress = (AFSocketAddress) addr;
this.setSocketAddress(socketAddress);
ByteBuffer ab = socketAddress.getNativeAddressDirectBuffer();
core.inode.set(NativeUnixSocket.bind(ab, ab.limit(), fd, options));
core.validFdOrException();
}
@Override
@SuppressWarnings("hiding")
protected final void bind(InetAddress host, int port) throws IOException {
// ignored
}
private void checkClose() throws IOException {
if (closedInputStream && closedOutputStream) {
close();
}
}
@Override
protected final void close() throws IOException {
shutdown();
core.runCleaner();
}
@Override
@SuppressWarnings("hiding")
protected final void connect(String host, int port) throws IOException {
throw new SocketException("Cannot bind to this type of address: " + InetAddress.class);
}
@Override
@SuppressWarnings("hiding")
protected final void connect(InetAddress address, int port) throws IOException {
throw new SocketException("Cannot bind to this type of address: " + InetAddress.class);
}
@Override
protected final void connect(SocketAddress addr, int connectTimeout) throws IOException {
connect0(addr, connectTimeout);
}
final boolean connect0(SocketAddress addr, int connectTimeout) throws IOException {
if (addr == AFSocketAddress.INTERNAL_DUMMY_CONNECT) { // NOPMD
this.connected.set(true);
return true;
} else if (addr == AFSocketAddress.INTERNAL_DUMMY_DONT_CONNECT) { // NOPMD)
return false;
}
if (!(addr instanceof AFSocketAddress)) {
throw new SocketException("Cannot connect to this type of address: " + addr.getClass());
}
AFSocketAddress socketAddress = (AFSocketAddress) addr;
ByteBuffer ab = socketAddress.getNativeAddressDirectBuffer();
boolean success = false;
boolean ignoreSpuriousTimeout = true;
do {
try {
success = NativeUnixSocket.connect(ab, ab.limit(), fd, -1);
break;
} catch (SocketTimeoutException e) {
// Ignore spurious timeout once when SO_TIMEOUT==0
// seen on older Linux kernels with AF_VSOCK running in qemu
if (ignoreSpuriousTimeout) {
Object o = getOption(SocketOptions.SO_TIMEOUT);
if (o instanceof Integer) {
if (((Integer) o) == 0) {
ignoreSpuriousTimeout = false;
continue;
}
} else if (o == null) {
ignoreSpuriousTimeout = false;
continue;
}
}
throw e;
}
} while (!Thread.interrupted());
if (success) {
setSocketAddress(socketAddress);
this.connected.set(true);
}
core.validFdOrException();
return success;
}
@Override
protected final void create(boolean stream) throws IOException {
if (isClosed()) {
throw new SocketException("Already closed");
}
if (fd.valid()) {
if (createType != null) {
if (createType.booleanValue() != stream) { // NOPMD.UnnecessaryBoxing
throw new IllegalStateException("Already created with different mode");
}
} else {
createType = stream;
}
return;
}
createType = stream;
createSocket(fd, stream ? AFSocketType.SOCK_STREAM : AFSocketType.SOCK_DGRAM);
}
@Override
protected final AFInputStream getInputStream() throws IOException {
if (!isConnected() && !isBound()) {
throw new SocketClosedException("Not connected/not bound");
}
core.validFdOrException();
return in;
}
@Override
protected final AFOutputStream getOutputStream() throws IOException {
if (!isClosed() && !isBound()) {
throw new SocketClosedException("Not connected/not bound");
}
core.validFdOrException();
return out;
}
@Override
protected final void listen(int backlog) throws IOException {
FileDescriptor fdesc = core.validFdOrException();
if (backlog <= 0) {
backlog = 50;
}
NativeUnixSocket.listen(fdesc, backlog);
}
@Override
protected final boolean supportsUrgentData() {
return false;
}
@Override
protected final void sendUrgentData(int data) throws IOException {
throw new UnsupportedOperationException();
}
private final class AFInputStreamImpl extends AFInputStream {
private volatile boolean streamClosed = false;
private final AtomicBoolean eofReached = new AtomicBoolean(false);
private final int opt = (core.isBlocking() ? 0 : NativeUnixSocket.OPT_NON_BLOCKING);
@Override
public int read(byte[] buf, int off, int len) throws IOException {
if (streamClosed) {
throw new SocketClosedException("This InputStream has already been closed.");
}
if (eofReached.get()) {
return -1;
}
FileDescriptor fdesc = core.validFdOrException();
if (len == 0) {
return 0;
} else if (off < 0 || len < 0 || (len > buf.length - off)) {
throw new IndexOutOfBoundsException();
}
try {
return NativeUnixSocket.read(fdesc, buf, off, len, opt, ancillaryDataSupport, socketTimeout
.get());
} catch (EOFException e) {
eofReached.set(true);
throw e;
}
}
@Override
public int read() throws IOException {
FileDescriptor fdesc = core.validFdOrException();
if (eofReached.get()) {
return -1;
}
int byteRead = NativeUnixSocket.read(fdesc, null, 0, 1, opt, ancillaryDataSupport,
socketTimeout.get());
if (byteRead < 0) {
eofReached.set(true);
return -1;
} else {
return byteRead;
}
}
@Override
public synchronized void close() throws IOException {
streamClosed = true;
FileDescriptor fdesc = core.validFd();
if (fdesc != null && getCore().isShutdownOnClose()) {
NativeUnixSocket.shutdown(fdesc, SHUT_RD);
}
closedInputStream = true;
checkClose();
}
@Override
public int available() throws IOException {
if (streamClosed) {
throw new SocketClosedException("This InputStream has already been closed.");
}
return AFSocketImpl.this.available();
}
@Override
public FileDescriptor getFileDescriptor() throws IOException {
return getFD();
}
}
private static boolean checkWriteInterruptedException(int bytesTransferred)
throws InterruptedIOException {
if (Thread.interrupted()) {
InterruptedIOException ex = new InterruptedIOException("Thread interrupted during write");
ex.bytesTransferred = bytesTransferred;
Thread.currentThread().interrupt();
throw ex;
}
return true;
}
private final class AFOutputStreamImpl extends AFOutputStream {
private volatile boolean streamClosed = false;
private final int opt = (core.isBlocking() ? 0 : NativeUnixSocket.OPT_NON_BLOCKING);
@Override
public void write(int oneByte) throws IOException {
FileDescriptor fdesc = core.validFdOrException();
int written;
do {
written = NativeUnixSocket.write(fdesc, null, oneByte, 1, opt, ancillaryDataSupport);
if (written != 0) {
break;
}
} while (checkWriteInterruptedException(0));
}
@Override
public void write(byte[] buf, int off, int len) throws IOException {
if (streamClosed) {
throw new SocketException("This OutputStream has already been closed.");
}
if (len < 0 || off < 0 || len > buf.length - off) {
throw new IndexOutOfBoundsException();
}
FileDescriptor fdesc = core.validFdOrException();
// NOTE: writing messages with len == 0 should be permissible (unless ignored in native code)
// For certain sockets, empty messages can be used to probe if the remote connection is alive
if (len == 0 && !AFSocket.supports(AFSocketCapability.CAPABILITY_ZERO_LENGTH_SEND)) {
return;
}
int writtenTotal = 0;
do {
final int written = NativeUnixSocket.write(fdesc, buf, off, len, opt, ancillaryDataSupport);
if (written < 0) {
if (len == 0) {
// This exception is only useful to detect OS-level bugs that we need to work-around
// in native code.
// throw new IOException("Error while writing zero-length byte array; try -D"
// + AFSocket.PROP_LIBRARY_DISABLE_CAPABILITY_PREFIX
// + AFSocketCapability.CAPABILITY_ZERO_LENGTH_SEND.name() + "=true");
// ignore
return;
} else {
throw new IOException("Unspecific error while writing");
}
}
len -= written;
off += written;
writtenTotal += written;
} while (len > 0 && checkWriteInterruptedException(writtenTotal));
}
@Override
public synchronized void close() throws IOException {
if (streamClosed) {
return;
}
streamClosed = true;
FileDescriptor fdesc = core.validFd();
if (fdesc != null && getCore().isShutdownOnClose()) {
NativeUnixSocket.shutdown(fdesc, SHUT_WR);
}
closedOutputStream = true;
checkClose();
}
@Override
public FileDescriptor getFileDescriptor() throws IOException {
return getFD();
}
}
@Override
public final String toString() {
return super.toString() + "[fd=" + fd + "; addr=" + this.core.socketAddress + "; connected="
+ connected + "; bound=" + bound + "]";
}
private static int expectInteger(Object value) throws SocketException {
if (value == null) {
throw (SocketException) new SocketException("Value must not be null").initCause(
new NullPointerException());
}
try {
return (Integer) value;
} catch (final ClassCastException e) {
throw (SocketException) new SocketException("Unsupported value: " + value).initCause(e);
}
}
private static int expectBoolean(Object value) throws SocketException {
if (value == null) {
throw (SocketException) new SocketException("Value must not be null").initCause(
new NullPointerException());
}
try {
return ((Boolean) value) ? 1 : 0;
} catch (final ClassCastException e) {
throw (SocketException) new SocketException("Unsupported value: " + value).initCause(e);
}
}
@Override
public Object getOption(int optID) throws SocketException {
return getOption0(optID);
}
private Object getOption0(int optID) throws SocketException {
if (isClosed()) {
throw new SocketException("Socket is closed");
}
if (optID == SocketOptions.SO_REUSEADDR) {
return reuseAddr;
}
FileDescriptor fdesc = core.validFdOrException();
return getOptionDefault(fdesc, optID, socketTimeout, addressFamily);
}
static final Object getOptionDefault(FileDescriptor fdesc, int optID, AtomicInteger acceptTimeout,
AFAddressFamily<?> af) throws SocketException {
try {
switch (optID) {
case SocketOptions.SO_KEEPALIVE:
try {
return (NativeUnixSocket.getSocketOptionInt(fdesc, optID) != 0);
} catch (SocketException e) {
// ignore
return false;
}
case SocketOptions.TCP_NODELAY:
return (NativeUnixSocket.getSocketOptionInt(fdesc, optID) != 0);
case SocketOptions.SO_TIMEOUT:
int v = Math.max(NativeUnixSocket.getSocketOptionInt(fdesc, 0x1005), NativeUnixSocket
.getSocketOptionInt(fdesc, 0x1006));
if (v == -1) {
// special value, meaning: do not override infinite timeout from native code
return 0;
}
return Math.max((acceptTimeout == null ? 0 : acceptTimeout.get()), v);
case SocketOptions.SO_LINGER:
case SocketOptions.SO_RCVBUF:
case SocketOptions.SO_SNDBUF:
return NativeUnixSocket.getSocketOptionInt(fdesc, optID);
case SocketOptions.IP_TOS:
return 0;
case SocketOptions.SO_BINDADDR:
return AFSocketAddress.getInetAddress(fdesc, false, af);
case SocketOptions.SO_REUSEADDR:
return false;
default:
throw new SocketException("Unsupported option: " + optID);
}
} catch (final SocketException e) {
throw e;
} catch (final Exception e) {
throw (SocketException) new SocketException("Could not get option").initCause(e);
}
}
@Override
public void setOption(int optID, Object value) throws SocketException {
setOption0(optID, value);
}
private void setOption0(int optID, Object value) throws SocketException {
if (isClosed()) {
throw new SocketException("Socket is closed");
}
if (optID == SocketOptions.SO_REUSEADDR) {
reuseAddr = (expectBoolean(value) != 0);
return;
}
FileDescriptor fdesc = core.validFdOrException();
setOptionDefault(fdesc, optID, value, socketTimeout);
}
/**
* Like {@link #getOption(int)}, but ignores exceptions for certain option IDs.
*
* @param optID The option ID.
* @return The value.
* @throws SocketException on error.
*/
protected final Object getOptionLenient(int optID) throws SocketException {
try {
return getOption0(optID);
} catch (SocketException e) {
switch (optID) {
case SocketOptions.TCP_NODELAY:
case SocketOptions.SO_KEEPALIVE:
return false;
default:
throw e;
}
}
}
/**
* Like {@link #setOption(int, Object)}, but ignores exceptions for certain option IDs.
*
* @param optID The option ID.
* @param value The value.
* @throws SocketException on error.
*/
protected final void setOptionLenient(int optID, Object value) throws SocketException {
try {
setOption0(optID, value);
} catch (SocketException e) {
switch (optID) {
case SocketOptions.TCP_NODELAY:
return;
default:
throw e;
}
}
}
static final void setOptionDefault(FileDescriptor fdesc, int optID, Object value,
AtomicInteger acceptTimeout) throws SocketException {
try {
switch (optID) {
case SocketOptions.SO_LINGER:
if (value instanceof Boolean) {
final boolean b = (Boolean) value;
if (b) {
throw new SocketException("Only accepting Boolean.FALSE here");
}
NativeUnixSocket.setSocketOptionInt(fdesc, optID, -1);
return;
}
NativeUnixSocket.setSocketOptionInt(fdesc, optID, expectInteger(value));
return;
case SocketOptions.SO_TIMEOUT: {
int timeout = expectInteger(value);
try {
NativeUnixSocket.setSocketOptionInt(fdesc, 0x1005, timeout);
} catch (InvalidArgumentSocketException e) {
// Perhaps the socket is shut down?
}
try {
NativeUnixSocket.setSocketOptionInt(fdesc, 0x1006, timeout);
} catch (InvalidArgumentSocketException e) {
// Perhaps the socket is shut down?
}
if (acceptTimeout != null) {
acceptTimeout.set(timeout);
}
return;
}
case SocketOptions.SO_RCVBUF:
case SocketOptions.SO_SNDBUF:
NativeUnixSocket.setSocketOptionInt(fdesc, optID, expectInteger(value));
return;
case SocketOptions.SO_KEEPALIVE:
try {
NativeUnixSocket.setSocketOptionInt(fdesc, optID, expectBoolean(value));
} catch (SocketException e) {
// ignore
}
return;
case SocketOptions.TCP_NODELAY:
NativeUnixSocket.setSocketOptionInt(fdesc, optID, expectBoolean(value));
return;
case SocketOptions.IP_TOS:
// ignore
return;
case SocketOptions.SO_REUSEADDR:
// ignore
return;
default:
throw new SocketException("Unsupported option: " + optID);
}
} catch (final SocketException e) {
throw e;
} catch (final Exception e) {
throw (SocketException) new SocketException("Error while setting option").initCause(e);
}
}
/**
* Shuts down both input and output at once. Equivalent to calling {@link #shutdownInput()} and
* {@link #shutdownOutput()}.
*
* @throws IOException on error.
*/
protected final void shutdown() throws IOException {
FileDescriptor fdesc = core.validFd();
if (fdesc != null) {
NativeUnixSocket.shutdown(fdesc, SHUT_RD_WR);
shutdownState = 0;
}
}
@Override
protected final void shutdownInput() throws IOException {
FileDescriptor fdesc = core.validFd();
if (fdesc != null) {
NativeUnixSocket.shutdown(fdesc, SHUT_RD);
shutdownState |= 1 << (SHUT_RD);
if (shutdownState == SHUTDOWN_RD_WR) {
NativeUnixSocket.shutdown(fdesc, SHUT_RD_WR);
shutdownState = 0;
}
}
}
@Override
protected final void shutdownOutput() throws IOException {
FileDescriptor fdesc = core.validFd();
if (fdesc != null) {
NativeUnixSocket.shutdown(fdesc, SHUT_WR);
shutdownState |= 1 << (SHUT_RD_WR);
if (shutdownState == SHUTDOWN_RD_WR) {
NativeUnixSocket.shutdown(fdesc, SHUT_RD_WR);
shutdownState = 0;
}
}
}
final int getAncillaryReceiveBufferSize() {
return ancillaryDataSupport.getAncillaryReceiveBufferSize();
}
final void setAncillaryReceiveBufferSize(int size) {
ancillaryDataSupport.setAncillaryReceiveBufferSize(size);
}
final void ensureAncillaryReceiveBufferSize(int minSize) {
ancillaryDataSupport.ensureAncillaryReceiveBufferSize(minSize);
}
AncillaryDataSupport getAncillaryDataSupport() {
return ancillaryDataSupport;
}
final SocketAddress receive(ByteBuffer dst) throws IOException {
return core.receive(dst);
}
final int send(ByteBuffer src, SocketAddress target) throws IOException {
return core.write(src, target, 0);
}
final int read(ByteBuffer dst, ByteBuffer socketAddressBuffer) throws IOException {
return core.read(dst, socketAddressBuffer, 0);
}
final int write(ByteBuffer src) throws IOException {
return core.write(src);
}
@Override
protected final FileDescriptor getFileDescriptor() {
return core.fd;
}
final void updatePorts(int local, int remote) {
this.localport = local;
if (remote >= 0) {
this.port = remote;
}
}
final @Nullable A getLocalSocketAddress() {
return AFSocketAddress.getSocketAddress(getFileDescriptor(), false, localport, addressFamily);
}
final @Nullable A getRemoteSocketAddress() {
return AFSocketAddress.getSocketAddress(getFileDescriptor(), true, port, addressFamily);
}
final int getLocalPort1() {
return localport;
}
final int getRemotePort() {
return port;
}
@Override
protected final InetAddress getInetAddress() {
@Nullable
A rsa = getRemoteSocketAddress();
if (rsa == null) {
return InetAddress.getLoopbackAddress();
} else {
return rsa.getInetAddress();
}
}
final void createSocket(FileDescriptor fdTarget, AFSocketType type) throws IOException {
NativeUnixSocket.createSocket(fdTarget, addressFamily.getDomain(), type.getId());
}
final AFAddressFamily<A> getAddressFamily() {
return addressFamily;
}
@Override
protected <T> void setOption(SocketOption<T> name, T value) throws IOException {
if (name instanceof AFSocketOption<?>) {
getCore().setOption((AFSocketOption<T>) name, value);
return;
}
Integer optionId = SocketOptionsMapper.resolve(name);
if (optionId == null) {
super.setOption(name, value);
} else {
setOption(optionId, value);
}
}
@SuppressWarnings("unchecked")
@Override
protected <T> T getOption(SocketOption<T> name) throws IOException {
if (name instanceof AFSocketOption<?>) {
return getCore().getOption((AFSocketOption<T>) name);
}
Integer optionId = SocketOptionsMapper.resolve(name);
if (optionId == null) {
return super.getOption(name);
} else {
return (T) getOption(optionId);
}
}
@Override
protected Set<SocketOption<?>> supportedOptions() {
return SocketOptionsMapper.SUPPORTED_SOCKET_OPTIONS;
}
/**
* Returns the internal helper instance for address-specific extensions.
*
* @return The helper instance.
* @throws UnsupportedOperationException if such extensions are not supported for this address
* type.
*/
protected final synchronized AFSocketImplExtensions<A> getImplExtensions() {
if (implExtensions == null) {
implExtensions = addressFamily.initImplExtensions(ancillaryDataSupport);
}
return implExtensions;
}
}