blob: 093551ae47c98e73fc2cc709ad63ad3356d72f28 [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 org.apache.harmony.nio.internal;
// BEGIN android-note
// In this class the address length was changed from long to int.
// END android-note
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.ConnectException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.DatagramSocketImpl;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
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 org.apache.harmony.luni.net.PlainDatagramSocketImpl;
import org.apache.harmony.luni.platform.FileDescriptorHandler;
import org.apache.harmony.luni.platform.INetworkSystem;
import org.apache.harmony.luni.platform.Platform;
import org.apache.harmony.nio.AddressUtil;
/*
* The default implementation class of java.nio.channels.DatagramChannel.
*/
class DatagramChannelImpl extends DatagramChannel implements FileDescriptorHandler {
// The singleton to do the native network operation.
private static final INetworkSystem networkSystem = Platform.getNetworkSystem();
private static final byte[] stubArray = new byte[0];
// The fd to interact with native code
private FileDescriptor fd;
// Our internal DatagramSocket.
private DatagramSocket socket = null;
// The address to be connected.
InetSocketAddress connectAddress = null;
// local port
private int localPort;
// At first, uninitialized.
boolean connected = false;
// whether the socket is bound
boolean isBound = false;
private static class ReadLock {}
private final Object readLock = new ReadLock();
private static class WriteLock {}
private final Object writeLock = new WriteLock();
/*
* Constructor
*/
protected DatagramChannelImpl(SelectorProvider selectorProvider) throws IOException {
super(selectorProvider);
fd = new FileDescriptor();
networkSystem.socket(fd, 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 (null == socket) {
socket = new DatagramSocketAdapter(new PlainDatagramSocketImpl(fd, localPort), this);
}
return socket;
}
/**
* Returns the local address from the IP stack. This method should not be
* called directly as it does not check the security policy.
*
* @return InetAddress the local address to which the socket is bound.
* @see DatagramSocket
*/
InetAddress getLocalAddress() {
return networkSystem.getSocketLocalAddress(fd);
}
/**
* @see java.nio.channels.DatagramChannel#isConnected()
*/
@Override
synchronized public boolean isConnected() {
return connected;
}
/**
* @see java.nio.channels.DatagramChannel#connect(java.net.SocketAddress)
*/
@Override
synchronized public DatagramChannel connect(SocketAddress address) throws IOException {
// must open
checkOpen();
// status must be un-connected.
if (connected) {
throw new IllegalStateException();
}
// check the address
InetSocketAddress inetSocketAddress = SocketChannelImpl.validateAddress(address);
// security check
SecurityManager sm = System.getSecurityManager();
if (null != sm) {
if (inetSocketAddress.getAddress().isMulticastAddress()) {
sm.checkMulticast(inetSocketAddress.getAddress());
} else {
sm.checkConnect(inetSocketAddress.getAddress().getHostName(),
inetSocketAddress.getPort());
}
}
try {
begin();
networkSystem.connect(fd,
inetSocketAddress.getAddress(), inetSocketAddress.getPort(), 0);
} catch (ConnectException e) {
// ConnectException means connect fail, not exception
} finally {
end(true);
}
// set the connected address.
connectAddress = inetSocketAddress;
connected = true;
isBound = true;
return this;
}
/**
* @see java.nio.channels.DatagramChannel#disconnect()
*/
@Override
synchronized public DatagramChannel disconnect() throws IOException {
if (!isConnected() || !isOpen()) {
return this;
}
connected = false;
connectAddress = null;
networkSystem.disconnectDatagram(fd);
if (null != socket) {
socket.disconnect();
}
return this;
}
@Override
public SocketAddress receive(ByteBuffer target) throws IOException {
FileChannelImpl.checkWritable(target);
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(null != retAddr);
}
return retAddr;
}
private SocketAddress receiveImpl(ByteBuffer target, boolean loop) throws IOException {
SocketAddress retAddr = null;
DatagramPacket receivePacket;
int oldposition = target.position();
int received = 0;
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 = networkSystem.recv(fd, receivePacket,
receivePacket.getData(), receivePacket.getOffset(), receivePacket.getLength(),
false, isConnected());
// security check
SecurityManager sm = System.getSecurityManager();
if (!isConnected() && null != sm) {
try {
sm.checkAccept(receivePacket.getAddress().getHostAddress(),
receivePacket.getPort());
} catch (SecurityException e) {
// do discard the datagram packet
receivePacket = null;
}
}
if (null != receivePacket && null != receivePacket.getAddress()) {
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(stubArray, 0);
int oldposition = target.position();
int received = 0;
do {
int address = AddressUtil.getDirectBufferAddress(target);
received = networkSystem.recvDirect(fd, receivePacket, address,
target.position(), target.remaining(), false, isConnected());
// security check
SecurityManager sm = System.getSecurityManager();
if (!isConnected() && null != sm) {
try {
sm.checkAccept(receivePacket.getAddress().getHostAddress(),
receivePacket.getPort());
} catch (SecurityException e) {
// do discard the datagram packet
receivePacket = null;
}
}
if (null != receivePacket && null != receivePacket.getAddress()) {
// copy the data of received packet
if (received > 0) {
target.position(oldposition + received);
}
retAddr = receivePacket.getSocketAddress();
break;
}
} while (loop);
return retAddr;
}
/**
* @see java.nio.channels.DatagramChannel#send(java.nio.ByteBuffer,
* java.net.SocketAddress)
*/
@Override
public int send(ByteBuffer source, SocketAddress socketAddress) throws IOException {
// must not null
checkNotNull(source);
// must open
checkOpen();
// transfer socketAddress
InetSocketAddress isa = (InetSocketAddress) socketAddress;
if (null == isa.getAddress()) {
throw new IOException();
}
if (isConnected()) {
if (!connectAddress.equals(isa)) {
throw new IllegalArgumentException();
}
} else {
// not connected, check security
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
if (isa.getAddress().isMulticastAddress()) {
sm.checkMulticast(isa.getAddress());
} else {
sm.checkConnect(isa.getAddress().getHostAddress(), isa.getPort());
}
}
}
// the return value.
int sendCount = 0;
try {
begin();
byte[] array = null;
int length = source.remaining();
int oldposition = source.position();
int start = oldposition;
if (source.isDirect()) {
synchronized (writeLock) {
int data_address = AddressUtil.getDirectBufferAddress(source);
sendCount = networkSystem.sendDirect(fd, data_address, start, length,
isa.getPort(), isa.getAddress());
}
} else {
if (source.hasArray()) {
array = source.array();
start += source.arrayOffset();
} else {
array = new byte[length];
source.get(array);
start = 0;
}
synchronized (writeLock) {
sendCount = networkSystem.send(fd, array, start, length,
isa.getPort(), isa.getAddress());
}
}
source.position(oldposition + sendCount);
return sendCount;
} finally {
end(sendCount >= 0);
}
}
@Override
public int read(ByteBuffer target) throws IOException {
FileChannelImpl.checkWritable(target);
checkOpenConnected();
if (!target.hasRemaining()) {
return 0;
}
int readCount = 0;
if (target.isDirect() || target.hasArray()) {
readCount = readImpl(target);
if (readCount > 0) {
target.position(target.position() + readCount);
}
} 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 {
if (length < 0 || offset < 0 || (long) length + (long) offset > targets.length) {
throw new IndexOutOfBoundsException();
}
// 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 readBuffer) throws IOException {
synchronized (readLock) {
int readCount = 0;
try {
begin();
int start = readBuffer.position();
int length = readBuffer.remaining();
if (readBuffer.isDirect()) {
int address = AddressUtil.getDirectBufferAddress(readBuffer);
readCount = networkSystem.recvDirect(fd, null, address, start, length,
false, isConnected());
} else {
// the target is assured to have array.
byte[] target = readBuffer.array();
start += readBuffer.arrayOffset();
readCount = networkSystem.recv(fd, null, target, start, length, false,
isConnected());
}
return readCount;
} catch (InterruptedIOException e) {
// InterruptedIOException will be thrown when timeout.
return 0;
} finally {
end(readCount > 0);
}
}
}
/**
* @see java.nio.channels.DatagramChannel#write(java.nio.ByteBuffer)
*/
@Override
public int write(ByteBuffer source) throws IOException {
// source buffer must be not null
checkNotNull(source);
// status must be open and connected
checkOpenConnected();
// return immediately if source is full
if (!source.hasRemaining()) {
return 0;
}
ByteBuffer writeBuffer = null;
byte[] writeArray = null;
int oldposition = source.position();
int result;
if (source.isDirect() || source.hasArray()) {
writeBuffer = source;
} else {
writeArray = new byte[source.remaining()];
source.get(writeArray);
writeBuffer = ByteBuffer.wrap(writeArray);
}
result = writeImpl(writeBuffer);
if (result > 0) {
source.position(oldposition + result);
}
return result;
}
/**
* @see java.nio.channels.DatagramChannel#write(java.nio.ByteBuffer[], int,
* int)
*/
@Override
public long write(ByteBuffer[] sources, int offset, int length) throws IOException {
if (length < 0 || offset < 0
|| (long) length + (long) offset > sources.length) {
throw new IndexOutOfBoundsException();
}
// 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;
}
/*
* Write the source. Return the count of bytes written.
*/
private int writeImpl(ByteBuffer buf) throws IOException {
synchronized (writeLock) {
int result = 0;
try {
begin();
int length = buf.remaining();
int start = buf.position();
if (buf.isDirect()) {
int address = AddressUtil.getDirectBufferAddress(buf);
result = networkSystem.sendDirect(fd, address, start, length, 0, null);
} else {
// buf is assured to have array.
start += buf.arrayOffset();
result = networkSystem.send(fd, buf.array(), start, length, 0, null);
}
return result;
} finally {
end(result > 0);
}
}
}
/*
* Do really closing action here.
*/
@Override
synchronized protected void implCloseSelectableChannel() throws IOException {
connected = false;
if (null != socket && !socket.isClosed()) {
socket.close();
} else {
networkSystem.close(fd);
}
}
@Override
protected void implConfigureBlocking(boolean blockingMode) throws IOException {
// Do nothing here. For real read/write operation in nonblocking mode,
// it uses select system call. Whether a channel is blocking can be
// decided by isBlocking() method.
}
/*
* Status check, must be open.
*/
private void checkOpen() throws IOException {
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 (null == source) {
throw new NullPointerException();
}
}
/*
* 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 DatagramChannelImpl channelImpl;
/*
* Constructor initialize the datagramSocketImpl and datagramChannelImpl
*/
DatagramSocketAdapter(DatagramSocketImpl socketimpl,
DatagramChannelImpl channelImpl) {
super(socketimpl);
this.channelImpl = channelImpl;
}
/*
* Get the internal datagramChannelImpl
*/
@Override
public DatagramChannel getChannel() {
return channelImpl;
}
/**
* @see java.net.DatagramSocket#isBound()
*/
@Override
public boolean isBound() {
return channelImpl.isBound;
}
/**
* @see java.net.DatagramSocket#isConnected()
*/
@Override
public boolean isConnected() {
return channelImpl.isConnected();
}
/**
* @see java.net.DatagramSocket#getInetAddress()
*/
@Override
public InetAddress getInetAddress() {
if (null == channelImpl.connectAddress) {
return null;
}
return channelImpl.connectAddress.getAddress();
}
/**
* @see java.net.DatagramSocket#getLocalAddress()
*/
@Override
public InetAddress getLocalAddress() {
return channelImpl.getLocalAddress();
}
/**
* @see java.net.DatagramSocket#getPort()
*/
@Override
public int getPort() {
if (null == channelImpl.connectAddress) {
return -1;
}
return channelImpl.connectAddress.getPort();
}
/**
* @see java.net.DatagramSocket#bind(java.net.SocketAddress)
*/
@Override
public void bind(SocketAddress localAddr) throws SocketException {
if (channelImpl.isConnected()) {
throw new AlreadyConnectedException();
}
super.bind(localAddr);
channelImpl.isBound = true;
}
/**
* @see java.net.DatagramSocket#receive(java.net.DatagramPacket)
*/
@Override
public void receive(DatagramPacket packet) throws IOException {
if (!channelImpl.isBlocking()) {
throw new IllegalBlockingModeException();
}
super.receive(packet);
}
/**
* @see java.net.DatagramSocket#send(java.net.DatagramPacket)
*/
@Override
public void send(DatagramPacket packet) throws IOException {
if (!channelImpl.isBlocking()) {
throw new IllegalBlockingModeException();
}
super.send(packet);
}
/**
* @see java.net.DatagramSocket#close()
*/
@Override
public void close() {
synchronized (channelImpl) {
if (channelImpl.isOpen()) {
try {
channelImpl.close();
} catch (IOException e) {
// Ignore
}
}
super.close();
}
}
/**
* @see java.net.DatagramSocket#disconnect()
*/
@Override
public void disconnect() {
try {
channelImpl.disconnect();
} catch (IOException e) {
// Ignore
}
super.disconnect();
}
}
}