/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 java.nio;
import android.system.ErrnoException;
import java.io.FileDescriptor;
import java.io.FilterInputStream;
import java.io.FilterOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.PlainSocketImpl;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.SocketUtils;
import java.nio.channels.AlreadyConnectedException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.ConnectionPendingException;
import java.nio.channels.IllegalBlockingModeException;
import java.nio.channels.NoConnectionPendingException;
import java.nio.channels.NotYetConnectedException;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.nio.channels.UnresolvedAddressException;
import java.nio.channels.UnsupportedAddressTypeException;
import java.util.Arrays;
import java.util.Set;
import libcore.io.IoBridge;
import libcore.io.IoUtils;
import libcore.io.Libcore;
import static android.system.OsConstants.*;
/*
* The default implementation class of java.nio.channels.SocketChannel.
*/
class SocketChannelImpl extends SocketChannel implements FileDescriptorChannel {
private static final int SOCKET_STATUS_UNINITIALIZED = -1;
// Status before connect.
private static final int SOCKET_STATUS_UNCONNECTED = 0;
// Status connection pending.
private static final int SOCKET_STATUS_PENDING = 1;
// Status after connection success.
private static final int SOCKET_STATUS_CONNECTED = 2;
// Status closed.
private static final int SOCKET_STATUS_CLOSED = 3;
private final FileDescriptor fd;
// Our internal Socket.
private SocketAdapter socket = null;
// The address to be connected.
private InetSocketAddress connectAddress = null;
// The local address the socket is bound to.
private InetAddress localAddress = null;
private int localPort;
private int status = SOCKET_STATUS_UNINITIALIZED;
// Whether the socket is bound.
private volatile boolean isBound = false;
private final Object readLock = new Object();
private final Object writeLock = new Object();
/*
* Constructor for creating a connected socket channel.
*/
public SocketChannelImpl(SelectorProvider selectorProvider) throws IOException {
this(selectorProvider, true);
}
/*
* Constructor for creating an optionally connected socket channel.
*/
public SocketChannelImpl(SelectorProvider selectorProvider, boolean connect) throws IOException {
super(selectorProvider);
status = SOCKET_STATUS_UNCONNECTED;
fd = (connect ? IoBridge.socket(true) : new FileDescriptor());
}
/*
* Constructor for use by Pipe.SinkChannel and Pipe.SourceChannel.
*/
public SocketChannelImpl(SelectorProvider selectorProvider, FileDescriptor existingFd) throws IOException {
super(selectorProvider);
status = SOCKET_STATUS_CONNECTED;
fd = existingFd;
}
/*
* Getting the internal Socket If we have not the socket, we create a new
* one.
*/
@Override
synchronized public Socket socket() {
if (socket == null) {
try {
InetAddress addr = null;
int port = 0;
if (connectAddress != null) {
addr = connectAddress.getAddress();
port = connectAddress.getPort();
}
socket = new SocketAdapter(new PlainSocketImpl(fd, localPort, addr, port), this);
} catch (SocketException e) {
return null;
}
}
return socket;
}
/**
* Initialise the isBound, localAddress and localPort state from the file descriptor. Used when
* some or all of the bound state has been left to the OS to decide, or when the Socket handled
* bind() or connect().
*
* @param updateSocketState
* if the associated socket (if present) needs to be updated
* @hide package visible for other nio classes
*/
void onBind(boolean updateSocketState) {
SocketAddress sa;
try {
sa = Libcore.os.getsockname(fd);
} catch (ErrnoException errnoException) {
throw new AssertionError(errnoException);
}
isBound = true;
InetSocketAddress localSocketAddress = (InetSocketAddress) sa;
localAddress = localSocketAddress.getAddress();
localPort = localSocketAddress.getPort();
if (updateSocketState && socket != null) {
socket.onBind(localAddress, localPort);
}
}
@Override
synchronized public boolean isConnected() {
return status == SOCKET_STATUS_CONNECTED;
}
@Override
synchronized public boolean isConnectionPending() {
return status == SOCKET_STATUS_PENDING;
}
@Override
public boolean connect(SocketAddress socketAddress) throws IOException {
// status must be open and unconnected
checkUnconnected();
// check the address
InetSocketAddress inetSocketAddress = validateAddress(socketAddress);
InetAddress normalAddr = inetSocketAddress.getAddress();
int port = inetSocketAddress.getPort();
// When connecting, map ANY address to localhost
if (normalAddr.isAnyLocalAddress()) {
normalAddr = InetAddress.getLocalHost();
}
boolean isBlocking = isBlocking();
boolean finished = false;
int newStatus;
try {
if (isBlocking) {
begin();
}
// When in blocking mode, IoBridge.connect() will return without an exception when the
// socket is connected. When in non-blocking mode it will return without an exception
// without knowing the result of the connection attempt, which could still be going on.
IoBridge.connect(fd, normalAddr, port);
newStatus = isBlocking ? SOCKET_STATUS_CONNECTED : SOCKET_STATUS_PENDING;
finished = true;
} catch (IOException e) {
if (isEINPROGRESS(e)) {
newStatus = SOCKET_STATUS_PENDING;
} else {
if (isOpen()) {
close();
finished = true;
}
throw e;
}
} finally {
if (isBlocking) {
end(finished);
}
}
// If the channel was not bound, a connection attempt will have caused an implicit bind() to
// take place. Keep the local address state held by the channel and the socket up to date.
if (!isBound) {
onBind(true /* updateSocketState */);
}
// Keep the connected state held by the channel and the socket up to date.
onConnectStatusChanged(inetSocketAddress, newStatus, true /* updateSocketState */);
return status == SOCKET_STATUS_CONNECTED;
}
/**
* Initialise the connect() state with the supplied information.
*
* @param updateSocketState
* if the associated socket (if present) needs to be updated
* @hide package visible for other nio classes
*/
void onConnectStatusChanged(InetSocketAddress address, int status, boolean updateSocketState) {
this.status = status;
connectAddress = address;
if (status == SOCKET_STATUS_CONNECTED && updateSocketState && socket != null) {
socket.onConnect(connectAddress.getAddress(), connectAddress.getPort());
}
}
private boolean isEINPROGRESS(IOException e) {
if (isBlocking()) {
return false;
}
if (e instanceof ConnectException) {
Throwable cause = e.getCause();
if (cause instanceof ErrnoException) {
return ((ErrnoException) cause).errno == EINPROGRESS;
}
}
return false;
}
@Override
public boolean finishConnect() throws IOException {
synchronized (this) {
if (!isOpen()) {
throw new ClosedChannelException();
}
if (status == SOCKET_STATUS_CONNECTED) {
return true;
}
if (status != SOCKET_STATUS_PENDING) {
throw new NoConnectionPendingException();
}
}
boolean finished = false;
try {
begin();
InetAddress inetAddress = connectAddress.getAddress();
int port = connectAddress.getPort();
finished = IoBridge.isConnected(fd, inetAddress, port, 0, 0); // Return immediately.
} catch (ConnectException e) {
if (isOpen()) {
close();
finished = true;
}
throw e;
} finally {
end(finished);
}
synchronized (this) {
status = (finished ? SOCKET_STATUS_CONNECTED : status);
if (finished && socket != null) {
socket.onConnect(connectAddress.getAddress(), connectAddress.getPort());
}
}
return finished;
}
@Override
public int read(ByteBuffer dst) throws IOException {
dst.checkWritable();
checkOpenConnected();
if (!dst.hasRemaining()) {
return 0;
}
return readImpl(dst);
}
@Override
public long read(ByteBuffer[] targets, int offset, int length) throws IOException {
Arrays.checkOffsetAndCount(targets.length, offset, length);
checkOpenConnected();
int totalCount = FileChannelImpl.calculateTotalRemaining(targets, offset, length, true);
if (totalCount == 0) {
return 0;
}
byte[] readArray = new byte[totalCount];
ByteBuffer readBuffer = ByteBuffer.wrap(readArray);
int readCount;
// read data to readBuffer, and then transfer data from readBuffer to targets.
readCount = readImpl(readBuffer);
readBuffer.flip();
if (readCount > 0) {
int left = readCount;
int index = offset;
// transfer data from readArray to targets
while (left > 0) {
int putLength = Math.min(targets[index].remaining(), left);
targets[index].put(readArray, readCount - left, putLength);
index++;
left -= putLength;
}
}
return readCount;
}
private int readImpl(ByteBuffer dst) throws IOException {
synchronized (readLock) {
int readCount = 0;
try {
if (isBlocking()) {
begin();
}
readCount = IoBridge.recvfrom(true, fd, dst, 0, null, false);
} finally {
if (isBlocking()) {
end(readCount > 0);
}
}
return readCount;
}
}
@Override
public int write(ByteBuffer src) throws IOException {
if (src == null) {
throw new NullPointerException("src == null");
}
checkOpenConnected();
if (!src.hasRemaining()) {
return 0;
}
return writeImpl(src);
}
@Override
public long write(ByteBuffer[] sources, int offset, int length) throws IOException {
Arrays.checkOffsetAndCount(sources.length, offset, length);
checkOpenConnected();
int count = FileChannelImpl.calculateTotalRemaining(sources, offset, length, false);
if (count == 0) {
return 0;
}
ByteBuffer writeBuf = ByteBuffer.allocate(count);
for (int val = offset; val < length + offset; val++) {
ByteBuffer source = sources[val];
int oldPosition = source.position();
writeBuf.put(source);
source.position(oldPosition);
}
writeBuf.flip();
int result = writeImpl(writeBuf);
int val = offset;
int written = result;
while (result > 0) {
ByteBuffer source = sources[val];
int gap = Math.min(result, source.remaining());
source.position(source.position() + gap);
val++;
result -= gap;
}
return written;
}
private int writeImpl(ByteBuffer src) throws IOException {
synchronized (writeLock) {
if (!src.hasRemaining()) {
return 0;
}
int writeCount = 0;
try {
if (isBlocking()) {
begin();
}
writeCount = IoBridge.sendto(fd, src, 0, null, 0);
} finally {
if (isBlocking()) {
end(writeCount >= 0);
}
}
return writeCount;
}
}
/*
* Status check, open and "connected", when read and write.
*/
synchronized private void checkOpenConnected() throws ClosedChannelException {
if (!isOpen()) {
throw new ClosedChannelException();
}
if (!isConnected()) {
throw new NotYetConnectedException();
}
}
/*
* Status check, open and "unconnected", before connection.
*/
synchronized private void checkUnconnected() throws IOException {
if (!isOpen()) {
throw new ClosedChannelException();
}
if (status == SOCKET_STATUS_CONNECTED) {
throw new AlreadyConnectedException();
}
if (status == SOCKET_STATUS_PENDING) {
throw new ConnectionPendingException();
}
}
/*
* Shared by this class and DatagramChannelImpl, to do the address transfer
* and check.
*/
static InetSocketAddress validateAddress(SocketAddress socketAddress) {
if (socketAddress == null) {
throw new IllegalArgumentException("socketAddress == null");
}
if (!(socketAddress instanceof InetSocketAddress)) {
throw new UnsupportedAddressTypeException();
}
InetSocketAddress inetSocketAddress = (InetSocketAddress) socketAddress;
if (inetSocketAddress.isUnresolved()) {
throw new UnresolvedAddressException();
}
return inetSocketAddress;
}
/*
* Do really closing action here.
*/
@Override
protected synchronized void implCloseSelectableChannel() throws IOException {
if (status != SOCKET_STATUS_CLOSED) {
status = SOCKET_STATUS_CLOSED;
// IoBridge.closeAndSignalBlockedThreads(fd) is idempotent: It is safe to call on an
// already-closed file descriptor.
IoBridge.closeAndSignalBlockedThreads(fd);
if (socket != null && !socket.isClosed()) {
socket.onClose();
}
}
}
@Override protected void implConfigureBlocking(boolean blocking) throws IOException {
IoUtils.setBlocking(fd, blocking);
}
/*
* Get the fd.
*/
public FileDescriptor getFD() {
return fd;
}
/* @hide used by ServerSocketChannelImpl to sync channel state during accept() */
public void onAccept(InetSocketAddress remoteAddress, boolean updateSocketState) {
onBind(updateSocketState);
onConnectStatusChanged(remoteAddress, SOCKET_STATUS_CONNECTED, updateSocketState);
}
/*
* Adapter classes for internal socket.
*/
private static class SocketAdapter extends Socket {
private final SocketChannelImpl channel;
private final PlainSocketImpl socketImpl;
SocketAdapter(PlainSocketImpl socketImpl, SocketChannelImpl channel)
throws SocketException {
super(socketImpl);
this.socketImpl = socketImpl;
this.channel = channel;
SocketUtils.setCreated(this);
// Sync state socket state with the channel it is being created from
if (channel.isBound) {
onBind(channel.localAddress, channel.localPort);
}
if (channel.isConnected()) {
onConnect(channel.connectAddress.getAddress(), channel.connectAddress.getPort());
}
if (!channel.isOpen()) {
onClose();
}
}
@Override
public SocketChannel getChannel() {
return channel;
}
@Override
public void connect(SocketAddress remoteAddr, int timeout) throws IOException {
if (!channel.isBlocking()) {
throw new IllegalBlockingModeException();
}
if (isConnected()) {
throw new AlreadyConnectedException();
}
super.connect(remoteAddr, timeout);
channel.onBind(false);
if (super.isConnected()) {
InetSocketAddress remoteInetAddress = (InetSocketAddress) remoteAddr;
channel.onConnectStatusChanged(
remoteInetAddress, SOCKET_STATUS_CONNECTED, false /* updateSocketState */);
}
}
@Override
public void bind(SocketAddress localAddr) throws IOException {
if (channel.isConnected()) {
throw new AlreadyConnectedException();
}
if (SocketChannelImpl.SOCKET_STATUS_PENDING == channel.status) {
throw new ConnectionPendingException();
}
super.bind(localAddr);
channel.onBind(false);
}
@Override
public void close() throws IOException {
synchronized (channel) {
super.close();
if (channel.isOpen()) {
// channel.close() recognizes the socket is closed and avoids recursion. There
// is no channel.onClose() because the "closed" field is private.
channel.close();
}
}
}
@Override
public OutputStream getOutputStream() throws IOException {
return new BlockingCheckOutputStream(super.getOutputStream(), channel);
}
@Override
public InputStream getInputStream() throws IOException {
return new BlockingCheckInputStream(super.getInputStream(), channel);
}
@Override
public FileDescriptor getFileDescriptor$() {
return socketImpl.getFD$();
}
}
/*
* Throws an IllegalBlockingModeException if the channel is in non-blocking
* mode when performing write operations.
*/
private static class BlockingCheckOutputStream extends FilterOutputStream {
private final SocketChannel channel;
public BlockingCheckOutputStream(OutputStream out, SocketChannel channel) {
super(out);
this.channel = channel;
}
@Override
public void write(byte[] buffer, int offset, int byteCount) throws IOException {
checkBlocking();
out.write(buffer, offset, byteCount);
}
@Override
public void write(int oneByte) throws IOException {
checkBlocking();
out.write(oneByte);
}
@Override
public void write(byte[] buffer) throws IOException {
checkBlocking();
out.write(buffer);
}
@Override
public void close() throws IOException {
super.close();
// channel.close() recognizes the socket is closed and avoids recursion. There is no
// channel.onClose() because the "closed" field is private.
channel.close();
}
private void checkBlocking() {
if (!channel.isBlocking()) {
throw new IllegalBlockingModeException();
}
}
}
/*
* Throws an IllegalBlockingModeException if the channel is in non-blocking
* mode when performing read operations.
*/
private static class BlockingCheckInputStream extends FilterInputStream {
private final SocketChannel channel;
public BlockingCheckInputStream(InputStream in, SocketChannel channel) {
super(in);
this.channel = channel;
}
@Override
public int read() throws IOException {
checkBlocking();
return in.read();
}
@Override
public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
checkBlocking();
return in.read(buffer, byteOffset, byteCount);
}
@Override
public int read(byte[] buffer) throws IOException {
checkBlocking();
return in.read(buffer);
}
@Override
public void close() throws IOException {
super.close();
// channel.close() recognizes the socket is closed and avoids recursion. There is no
// channel.onClose() because the "closed" field is private.
channel.close();
}
private void checkBlocking() {
if (!channel.isBlocking()) {
throw new IllegalBlockingModeException();
}
}
}
}