package server;
import java.io.EOFException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.LinkedList;
import javax.crypto.Cipher;
import server.token.ResolvedTokenString;
import common.ByteSource;
class Client implements Readable, Writable {
private final SelectionKey key;
private final SocketChannel channel;
private final Cipher encipher;
private final Cipher decipher;
private final ByteBuffer lengthIndicatorBuffer = ByteBuffer.allocate(4);
private LinkedList<ByteSource> writeQueue = new LinkedList<ByteSource>();
private ByteBuffer[] writeBuffers = new ByteBuffer[] {};
private int writeOffset;
private ByteBuffer readBuffer = lengthIndicatorBuffer;
private ResolvedTokenString lastRTS = null;
private boolean closed = false;
public static Client currentClient;
private ClientState clientState;
public Client(SelectionKey key, ClientState clientState, Cipher encipher, Cipher decipher) {
this.key = key;
this.channel = (SocketChannel) key.channel();
this.clientState = clientState;
this.encipher = encipher;
this.decipher = decipher;
key.interestOps(SelectionKey.OP_READ);
}
public void close() {
if (closed) return;
closed = true;
key.cancel();
try { key.channel().close(); } catch (IOException ex) { }
clientState.close(false);
}
public void doRead() {
try {
if (channel.read(readBuffer) < 0) { throw new EOFException();
}
while (!readBuffer.hasRemaining()) {
if (readBuffer == lengthIndicatorBuffer) {
int length = readBuffer.getInt(0);
Utils.checkInputLength(length);
readBuffer = ByteBuffer.allocate(length);
}
else {
byte[] b = readBuffer.array();
b = decipher.doFinal(b);
clientState.process(b);
readBuffer = lengthIndicatorBuffer;
readBuffer.clear();
}
}
}
catch (Exception ex) {
ex.printStackTrace();
close();
}
}
public void doWrite() {
try {
if (writeOffset >= writeBuffers.length) {
writeBuffers = new ByteBuffer[writeQueue.size() * 2];
int ptr = 0;
for (ByteSource bs : writeQueue) {
if (bs == lastRTS) {
lastRTS = null; }
byte[] b = bs.toByteArray();
b = encipher.doFinal(b);
writeBuffers[ptr] = ByteBuffer.allocate(4);
writeBuffers[ptr++].putInt(0, b.length);
writeBuffers[ptr++] = ByteBuffer.wrap(b);
}
writeOffset = 0;
writeQueue = new LinkedList<ByteSource>();
if (writeBuffers.length == 0) {
key.interestOps(SelectionKey.OP_READ);
return; }
}
channel.write(writeBuffers, writeOffset, writeBuffers.length - writeOffset);
int idx;
for (idx = writeOffset; idx < writeBuffers.length; idx++) {
if (writeBuffers[idx].hasRemaining()) {
break;
}
writeBuffers[idx] = null;
}
writeOffset = idx;
}
catch (Exception ex) {
ex.printStackTrace();
close();
}
}
void send(ByteSource bs) {
if (bs instanceof ResolvedTokenString) {
ResolvedTokenString rst = (ResolvedTokenString) bs;
if (lastRTS != null && lastRTS.combineWith(rst)) {
return;
}
lastRTS = rst;
}
writeQueue.add(bs);
key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
}
}