blob: 8dd6f29688e4918e7c96198202b20b4ab999a283 [file] [log] [blame]
/*
* 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.InterruptedIOException;
import java.io.IOException;
import java.net.ConnectException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.DatagramSocketImpl;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.PlainDatagramSocketImpl;
import java.net.SocketAddress;
import java.net.SocketException;
import java.nio.channels.AlreadyConnectedException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.DatagramChannel;
import java.nio.channels.IllegalBlockingModeException;
import java.nio.channels.NotYetConnectedException;
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 libcore.util.EmptyArray;
/*
* The default implementation class of java.nio.channels.DatagramChannel.
*/
class DatagramChannelImpl extends DatagramChannel implements FileDescriptorChannel {
// The fd to interact with native code
private final FileDescriptor fd;
// Our internal DatagramSocket.
private DatagramSocket socket;
// The remote address to be connected.
InetSocketAddress connectAddress;
// The local address.
InetAddress localAddress;
// local port
private int localPort;
// At first, uninitialized.
boolean connected = false;
// whether the socket is bound
boolean isBound = false;
private final Object readLock = new Object();
private final Object writeLock = new Object();
/*
* Constructor
*/
protected DatagramChannelImpl(SelectorProvider selectorProvider) throws IOException {
super(selectorProvider);
fd = IoBridge.socket(false);
}
/*
* for native call
*/
@SuppressWarnings("unused")
private DatagramChannelImpl() {
super(SelectorProvider.provider());
fd = new FileDescriptor();
connectAddress = new InetSocketAddress(0);
}
/*
* Getting the internal DatagramSocket If we have not the socket, we create
* a new one.
*/
@Override
synchronized public DatagramSocket socket() {
if (socket == null) {
socket = new DatagramSocketAdapter(new PlainDatagramSocketImpl(fd, localPort), this);
}
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 used to sync state, non-private to avoid synthetic method
*/
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 connected;
}
@Override
synchronized public DatagramChannel connect(SocketAddress address) throws IOException {
// must be open
checkOpen();
// status must be un-connected.
if (connected) {
throw new IllegalStateException();
}
// check the address
InetSocketAddress inetSocketAddress = SocketChannelImpl.validateAddress(address);
InetAddress remoteAddress = inetSocketAddress.getAddress();
int remotePort = inetSocketAddress.getPort();
try {
begin();
IoBridge.connect(fd, remoteAddress, remotePort);
} catch (ConnectException e) {
// ConnectException means connect fail, not exception
} finally {
end(true);
}
// connect() performs a bind() if an explicit bind() was not performed. 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.
onConnect(remoteAddress, remotePort, true /* updateSocketState */);
return this;
}
/**
* Initialize the state associated with being connected, optionally syncing the socket if there
* is one.
* @hide used to sync state, non-private to avoid synthetic method
*/
void onConnect(InetAddress remoteAddress, int remotePort, boolean updateSocketState) {
connected = true;
connectAddress = new InetSocketAddress(remoteAddress, remotePort);
if (updateSocketState && socket != null) {
socket.onConnect(remoteAddress, remotePort);
}
}
@Override
synchronized public DatagramChannel disconnect() throws IOException {
if (!isConnected() || !isOpen()) {
return this;
}
// Keep the disconnected state held by the channel and the socket up to date.
onDisconnect(true /* updateSocketState */);
try {
Libcore.os.connect(fd, InetAddress.UNSPECIFIED, 0);
} catch (ErrnoException errnoException) {
throw errnoException.rethrowAsIOException();
}
return this;
}
/**
* Initialize the state associated with being disconnected, optionally syncing the socket if
* there is one.
* @hide used to sync state, non-private to avoid synthetic method
*/
void onDisconnect(boolean updateSocketState) {
connected = false;
connectAddress = null;
if (updateSocketState && socket != null && socket.isConnected()) {
socket.onDisconnect();
}
}
@Override
public SocketAddress receive(ByteBuffer target) throws IOException {
target.checkWritable();
checkOpen();
if (!isBound) {
return null;
}
SocketAddress retAddr = null;
try {
begin();
// receive real data packet, (not peek)
synchronized (readLock) {
boolean loop = isBlocking();
if (!target.isDirect()) {
retAddr = receiveImpl(target, loop);
} else {
retAddr = receiveDirectImpl(target, loop);
}
}
} catch (InterruptedIOException e) {
// this line used in Linux
return null;
} finally {
end(retAddr != null);
}
return retAddr;
}
private SocketAddress receiveImpl(ByteBuffer target, boolean loop) throws IOException {
SocketAddress retAddr = null;
DatagramPacket receivePacket;
int oldposition = target.position();
int received;
// TODO: disallow mapped buffers and lose this conditional?
if (target.hasArray()) {
receivePacket = new DatagramPacket(target.array(), target.position() + target.arrayOffset(), target.remaining());
} else {
receivePacket = new DatagramPacket(new byte[target.remaining()], target.remaining());
}
do {
received = IoBridge.recvfrom(false, fd, receivePacket.getData(), receivePacket.getOffset(), receivePacket.getLength(), 0, receivePacket, isConnected());
if (receivePacket.getAddress() != null) {
if (received > 0) {
if (target.hasArray()) {
target.position(oldposition + received);
} else {
// copy the data of received packet
target.put(receivePacket.getData(), 0, received);
}
}
retAddr = receivePacket.getSocketAddress();
break;
}
} while (loop);
return retAddr;
}
private SocketAddress receiveDirectImpl(ByteBuffer target, boolean loop) throws IOException {
SocketAddress retAddr = null;
DatagramPacket receivePacket = new DatagramPacket(EmptyArray.BYTE, 0);
do {
IoBridge.recvfrom(false, fd, target, 0, receivePacket, isConnected());
if (receivePacket.getAddress() != null) {
retAddr = receivePacket.getSocketAddress();
break;
}
} while (loop);
return retAddr;
}
@Override
public int send(ByteBuffer source, SocketAddress socketAddress) throws IOException {
checkNotNull(source);
checkOpen();
InetSocketAddress isa = (InetSocketAddress) socketAddress;
if (isa.getAddress() == null) {
throw new IOException();
}
if (isConnected() && !connectAddress.equals(isa)) {
throw new IllegalArgumentException("Connected to " + connectAddress +
", not " + socketAddress);
}
synchronized (writeLock) {
int sendCount = 0;
try {
begin();
sendCount = IoBridge.sendto(fd, source, 0, isa.getAddress(), isa.getPort());
if (!isBound) {
onBind(true /* updateSocketState */);
}
} finally {
end(sendCount >= 0);
}
return sendCount;
}
}
@Override
public int read(ByteBuffer target) throws IOException {
target.checkWritable();
checkOpenConnected();
if (!target.hasRemaining()) {
return 0;
}
int readCount;
if (target.isDirect() || target.hasArray()) {
readCount = readImpl(target);
} else {
byte[] readArray = new byte[target.remaining()];
ByteBuffer readBuffer = ByteBuffer.wrap(readArray);
readCount = readImpl(readBuffer);
if (readCount > 0) {
target.put(readArray, 0, readCount);
}
}
return readCount;
}
@Override
public long read(ByteBuffer[] targets, int offset, int length) throws IOException {
Arrays.checkOffsetAndCount(targets.length, offset, length);
// status must be open and connected
checkOpenConnected();
int totalCount = FileChannelImpl.calculateTotalRemaining(targets, offset, length, true);
if (totalCount == 0) {
return 0;
}
// read data to readBuffer, and then transfer data from readBuffer to
// targets.
ByteBuffer readBuffer = ByteBuffer.allocate(totalCount);
int readCount;
readCount = readImpl(readBuffer);
int left = readCount;
int index = offset;
// transfer data from readBuffer to targets
byte[] readArray = readBuffer.array();
while (left > 0) {
int putLength = Math.min(targets[index].remaining(), left);
targets[index].put(readArray, readCount - left, putLength);
index++;
left -= putLength;
}
return readCount;
}
/*
* read from channel, and store the result in the target.
*/
private int readImpl(ByteBuffer dst) throws IOException {
synchronized (readLock) {
int readCount = 0;
try {
begin();
readCount = IoBridge.recvfrom(false, fd, dst, 0, null, isConnected());
} catch (InterruptedIOException e) {
// InterruptedIOException will be thrown when timeout.
return 0;
} finally {
end(readCount > 0);
}
return readCount;
}
}
@Override public int write(ByteBuffer src) throws IOException {
checkNotNull(src);
checkOpenConnected();
if (!src.hasRemaining()) {
return 0;
}
return writeImpl(src);
}
/**
* @see java.nio.channels.DatagramChannel#write(java.nio.ByteBuffer[], int,
* int)
*/
@Override
public long write(ByteBuffer[] sources, int offset, int length) throws IOException {
Arrays.checkOffsetAndCount(sources.length, offset, length);
// status must be open and connected
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 buf) throws IOException {
synchronized (writeLock) {
int result = 0;
try {
begin();
result = IoBridge.sendto(fd, buf, 0, null, 0);
} finally {
end(result > 0);
}
return result;
}
}
@Override protected synchronized void implCloseSelectableChannel() throws IOException {
// A closed channel is not connected.
onDisconnect(true /* updateSocketState */);
IoBridge.closeAndSignalBlockedThreads(fd);
if (socket != null && !socket.isClosed()) {
socket.onClose();
}
}
@Override protected void implConfigureBlocking(boolean blocking) throws IOException {
IoUtils.setBlocking(fd, blocking);
}
/*
* Status check, must be open.
*/
private void checkOpen() throws ClosedChannelException {
if (!isOpen()) {
throw new ClosedChannelException();
}
}
/*
* Status check, must be open and connected, for read and write.
*/
private void checkOpenConnected() throws IOException {
checkOpen();
if (!isConnected()) {
throw new NotYetConnectedException();
}
}
/*
* Buffer check, must not null
*/
private void checkNotNull(ByteBuffer source) {
if (source == null) {
throw new NullPointerException("source == null");
}
}
/*
* Get the fd for internal use.
*/
public FileDescriptor getFD() {
return fd;
}
/*
* The adapter class of DatagramSocket
*/
private static class DatagramSocketAdapter extends DatagramSocket {
/*
* The internal datagramChannelImpl.
*/
private final DatagramChannelImpl channelImpl;
/*
* Constructor initialize the datagramSocketImpl and datagramChannelImpl
*/
DatagramSocketAdapter(DatagramSocketImpl socketimpl, DatagramChannelImpl channelImpl) {
super(socketimpl);
this.channelImpl = channelImpl;
// Sync state socket state with the channel it is being created from
if (channelImpl.isBound) {
onBind(channelImpl.localAddress, channelImpl.localPort);
}
if (channelImpl.connected) {
onConnect(
channelImpl.connectAddress.getAddress(),
channelImpl.connectAddress.getPort());
} else {
onDisconnect();
}
if (!channelImpl.isOpen()) {
onClose();
}
}
/*
* Get the internal datagramChannelImpl
*/
@Override
public DatagramChannel getChannel() {
return channelImpl;
}
@Override
public void bind(SocketAddress localAddr) throws SocketException {
if (channelImpl.isConnected()) {
throw new AlreadyConnectedException();
}
super.bind(localAddr);
channelImpl.onBind(false /* updateSocketState */);
}
@Override
public void connect(SocketAddress peer) throws SocketException {
if (isConnected()) {
// RI compatibility: If the socket is already connected this fails.
throw new IllegalStateException("Socket is already connected.");
}
super.connect(peer);
// Connect may have performed an implicit bind(). Sync up here.
channelImpl.onBind(false /* updateSocketState */);
InetSocketAddress inetSocketAddress = (InetSocketAddress) peer;
channelImpl.onConnect(
inetSocketAddress.getAddress(), inetSocketAddress.getPort(),
false /* updateSocketState */);
}
@Override
public void connect(InetAddress address, int port) {
// To avoid implementing connect() twice call this.connect(SocketAddress) in preference
// to super.connect().
try {
connect(new InetSocketAddress(address, port));
} catch (SocketException e) {
// Ignored - there is nothing we can report here.
}
}
@Override
public void receive(DatagramPacket packet) throws IOException {
if (!channelImpl.isBlocking()) {
throw new IllegalBlockingModeException();
}
boolean wasBound = isBound();
super.receive(packet);
if (!wasBound) {
// DatagramSocket.receive() will implicitly bind if it hasn't been done explicitly.
// Sync the channel state with the socket.
channelImpl.onBind(false /* updateSocketState */);
}
}
@Override
public void send(DatagramPacket packet) throws IOException {
if (!channelImpl.isBlocking()) {
throw new IllegalBlockingModeException();
}
// DatagramSocket.send() will implicitly bind if it hasn't been done explicitly. Force
// bind() here so that the channel state stays in sync with the socket.
boolean wasBound = isBound();
super.send(packet);
if (!wasBound) {
// DatagramSocket.send() will implicitly bind if it hasn't been done explicitly.
// Sync the channel state with the socket.
channelImpl.onBind(false /* updateSocketState */);
}
}
@Override
public void close() {
synchronized (channelImpl) {
super.close();
if (channelImpl.isOpen()) {
try {
channelImpl.close();
} catch (IOException e) {
// Ignore
}
}
}
}
@Override
public void disconnect() {
super.disconnect();
channelImpl.onDisconnect(false /* updateSocketState */);
}
}
}