| /* |
| * 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); |
| if (readCount > 0) { |
| dst.position(dst.position() + readCount); |
| } |
| } 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); |
| if (writeCount > 0) { |
| src.position(src.position() + writeCount); |
| } |
| } 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(); |
| } |
| } |
| } |
| } |