package games.strategy.net.nio;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A packet of data being read over the network.
*
* <p>
* A Packet does not correspond to a network packet, rather it is the bytes for 1 serialized java object.
* </p>
*/
class SocketReadData {
public static final int MAX_MESSAGE_SIZE = 1000 * 1000 * 10;
private static final Logger logger = Logger.getLogger(SocketReadData.class.getName());
private static final AtomicInteger counter = new AtomicInteger();
// as a sanity check to make sure
// we are talking to another tripea instance
// that the upper bits of the packet
// size we send is 0x9b
public static final int MAGIC = 0x9b000000;
private int targetSize = -1;
// we read into here the first four
// bytes to find out size
private ByteBuffer sizeBuffer;
// we read into here after knowing out size
private ByteBuffer contentBuffer;
private final SocketChannel channel;
private final int number = counter.incrementAndGet();
private int readCalls;
public SocketReadData(final SocketChannel channel) {
this.channel = channel;
}
/**
* Read data from the channel, returning true if this packet is done.
*
* <p>
* If we detect the socket is closed, we will throw an IOExcpetion
* </p>
*/
public boolean read(final SocketChannel channel) throws IOException {
readCalls++;
// we dont know our size, read it
if (targetSize < 0) {
// our first read
// find out how big this packet is
if (sizeBuffer == null) {
sizeBuffer = ByteBuffer.allocate(4);
}
final int size = channel.read(sizeBuffer);
if (logger.isLoggable(Level.FINEST)) {
logger.finest("read size_buffer bytes:" + size);
}
if (size == -1) {
throw new IOException("Socket closed");
}
// we have read all four bytes of our size
if (!sizeBuffer.hasRemaining()) {
sizeBuffer.flip();
targetSize = sizeBuffer.getInt();
if ((targetSize & 0xFF000000) != MAGIC) {
throw new IOException("Did not write magic!");
}
targetSize = targetSize & 0x00ffffff;
// limit messages to 10MB
if (targetSize <= 0 || targetSize > MAX_MESSAGE_SIZE) {
throw new IOException("Invalid triplea packet size:" + targetSize);
}
contentBuffer = ByteBuffer.allocate(targetSize);
sizeBuffer = null;
} else {
// we ddnt read all 4 bytes, return
return false;
}
}
// http://javaalmanac.com/egs/java.nio/DetectClosed.html
final int size = channel.read(contentBuffer);
if (logger.isLoggable(Level.FINEST)) {
logger.finest("read content bytes:" + size);
}
if (size == -1) {
throw new IOException("Socket closed");
}
return !contentBuffer.hasRemaining();
}
public SocketChannel getChannel() {
return channel;
}
/**
* Get the data as a byte[].
* This method can only be called once.
*/
public byte[] getData() {
final byte[] rVal = new byte[contentBuffer.capacity()];
contentBuffer.flip();
contentBuffer.get(rVal);
contentBuffer = null;
return rVal;
}
public int size() {
// add 4 to count the bytes used to send our size
return targetSize + 4;
}
public int getReadCalls() {
return readCalls;
}
@Override
public String toString() {
return "<id:" + number + " size:" + targetSize + ">";
}
}