package edu.washington.cs.oneswarm.f2f.messaging;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import org.gudy.azureus2.core3.util.Debug;
import org.gudy.azureus2.core3.util.DirectByteBuffer;
import org.gudy.azureus2.core3.util.DirectByteBufferPool;
import com.aelitis.azureus.core.networkmanager.Transport;
import com.aelitis.azureus.core.peermanager.messaging.Message;
import com.aelitis.azureus.core.peermanager.messaging.MessageException;
import com.aelitis.azureus.core.peermanager.messaging.MessageManager;
import com.aelitis.azureus.core.peermanager.messaging.MessageStreamDecoder;
import com.aelitis.azureus.core.peermanager.messaging.bittorrent.BTMessage;
public class OSF2FMessageDecoder implements MessageStreamDecoder {
private static final int MIN_MESSAGE_LENGTH = 1; // for type id
// should never be > 16KB+9B, as we never request chunks > 16KB
// private static final int MAX_MESSAGE_LENGTH = 16393;
// EDIT: actually, file lists and torrents can be in the MB
private static final int MAX_MESSAGE_LENGTH = 1024 * 1024;// 16401;
// (byte)19 +"Bit" readInt() value of header
// private static final int HANDSHAKE_FAKE_LENGTH = 323119476;
// EDIT: (byte) 12 + "One" readInt() value of header
private static final int HANDSHAKE_FAKE_LENGTH = 206532197;
private static final byte SS = DirectByteBuffer.SS_MSG;
private DirectByteBuffer payload_buffer = null;
private final DirectByteBuffer length_buffer = DirectByteBufferPool.getBuffer(
DirectByteBuffer.AL_MSG, 4);
private final ByteBuffer[] decode_array = new ByteBuffer[] { null, length_buffer.getBuffer(SS) };
private boolean reading_length_mode = true;
private boolean reading_handshake_message = false;
private int message_length;
private int pre_read_start_buffer;
private int pre_read_start_position;
private boolean last_received_was_keepalive = false;
private volatile boolean destroyed = false;
private volatile boolean is_paused = false;
private ArrayList<Message> messages_last_read = new ArrayList<Message>();
private int protocol_bytes_last_read = 0;
private int data_bytes_last_read = 0;
private int percent_complete = -1;
public OSF2FMessageDecoder() {
/* nothing */
}
public int performStreamDecode(Transport transport, int max_bytes) throws IOException {
protocol_bytes_last_read = 0;
data_bytes_last_read = 0;
int bytes_remaining = max_bytes;
while (bytes_remaining > 0) {
if (destroyed) {
// destruction currently isn't thread safe so one thread can
// destroy the decoder (e.g. when closing a connection)
// while the read-controller is still actively processing the us
// throw( new IOException( "BTMessageDecoder already destroyed"
// ));
break;
}
if (is_paused) {
break;
}
int bytes_possible = preReadProcess(bytes_remaining);
if (bytes_possible < 1) {
Debug.out("ERROR OS: bytes_possible < 1");
break;
}
if (reading_length_mode) {
transport.read(decode_array, 1, 1); // only read into length
// buffer
} else {
transport.read(decode_array, 0, 2); // read into payload buffer,
// and possibly next message
// length
}
int bytes_read = postReadProcess();
bytes_remaining -= bytes_read;
if (bytes_read < bytes_possible) {
break;
}
if (reading_length_mode && last_received_was_keepalive) {
// hack to stop a 0-byte-read after receiving a keep-alive
// message
// otherwise we won't realize there's nothing left on the line
// until trying to read again
last_received_was_keepalive = false;
break;
}
}
return max_bytes - bytes_remaining;
}
public int getPercentDoneOfCurrentMessage() {
return percent_complete;
}
public Message[] removeDecodedMessages() {
if (messages_last_read.isEmpty())
return null;
Message[] msgs = (Message[]) messages_last_read.toArray(new Message[messages_last_read
.size()]);
messages_last_read.clear();
return msgs;
}
public int getProtocolBytesDecoded() {
return protocol_bytes_last_read;
}
public int getDataBytesDecoded() {
return data_bytes_last_read;
}
public ByteBuffer destroy() {
is_paused = true;
destroyed = true;
int lbuff_read = 0;
int pbuff_read = 0;
if (length_buffer != null) {
length_buffer.limit(SS, 4);
if (reading_length_mode) {
lbuff_read = length_buffer.position(SS);
} else { // reading payload
length_buffer.position(SS, 4);
lbuff_read = 4;
pbuff_read = payload_buffer == null ? 0 : payload_buffer.position(SS);
}
ByteBuffer unused = ByteBuffer.allocate(lbuff_read + pbuff_read); // TODO
// convert
// to
// direct?
length_buffer.flip(SS);
unused.put(length_buffer.getBuffer(SS));
if (payload_buffer != null) {
payload_buffer.flip(SS);
unused.put(payload_buffer.getBuffer(SS));
}
unused.flip();
length_buffer.returnToPool();
if (payload_buffer != null) {
payload_buffer.returnToPool();
payload_buffer = null;
}
for (int i = 0; i < messages_last_read.size(); i++) {
Message msg = (Message) messages_last_read.get(i);
msg.destroy();
}
messages_last_read.clear();
return unused;
}
return ByteBuffer.allocate(0);
}
private int preReadProcess(int allowed) {
if (allowed < 1) {
Debug.out("allowed < 1");
}
decode_array[0] = payload_buffer == null ? null : payload_buffer.getBuffer(SS); // ensure
// the
// decode
// array
// has
// the
// latest
// payload pointer
int bytes_available = 0;
boolean shrink_remaining_buffers = false;
int start_buff = reading_length_mode ? 1 : 0;
boolean marked = false;
for (int i = start_buff; i < 2; i++) { // set buffer limits according
// to bytes allowed
ByteBuffer bb = decode_array[i];
if (bb == null) {
Debug.out("preReadProcess:: bb[" + i + "] == null, decoder destroyed=" + destroyed);
}
if (shrink_remaining_buffers) {
bb.limit(0); // ensure no read into this next buffer is
// possible
} else {
int remaining = bb.remaining();
if (remaining < 1)
continue; // skip full buffer
if (!marked) {
pre_read_start_buffer = i;
pre_read_start_position = bb.position();
marked = true;
}
if (remaining > allowed) { // read only part of this buffer
bb.limit(bb.position() + allowed); // limit current buffer
bytes_available += bb.remaining();
shrink_remaining_buffers = true; // shrink any tail
// buffers
} else { // full buffer is allowed to be read
bytes_available += remaining;
allowed -= remaining; // count this buffer toward allowed
// and move on to the next
}
}
}
return bytes_available;
}
private int postReadProcess() throws IOException {
int prot_bytes_read = 0;
int data_bytes_read = 0;
if (!reading_length_mode && !destroyed) { // reading payload data mode
// ensure-restore proper buffer limits
payload_buffer.limit(SS, message_length);
length_buffer.limit(SS, 4);
int read = payload_buffer.position(SS) - pre_read_start_position;
if (payload_buffer.position(SS) > 0) { // need to have read the
// message id first byte
if (OSF2FMessageFactory.getMessageType(payload_buffer) == Message.TYPE_DATA_PAYLOAD) {
data_bytes_read += read;
} else {
prot_bytes_read += read;
}
}
if (!payload_buffer.hasRemaining(SS) && !is_paused) { // full
// message received!
payload_buffer.position(SS, 0);
DirectByteBuffer ref_buff = payload_buffer;
payload_buffer = null;
if (reading_handshake_message) { // decode handshake
reading_handshake_message = false;
DirectByteBuffer handshake_data = DirectByteBufferPool.getBuffer(
DirectByteBuffer.AL_MSG, OSF2FHandshake.MESSAGE_LENGTH);
handshake_data.putInt(SS, HANDSHAKE_FAKE_LENGTH);
handshake_data.put(SS, ref_buff);
handshake_data.flip(SS);
ref_buff.returnToPool();
try {
Message handshake = MessageManager.getSingleton().createMessage(
OSF2FMessage.ID_OS_HANDSHAKE_BYTES, handshake_data, (byte) 1);
messages_last_read.add(handshake);
} catch (MessageException me) {
handshake_data.returnToPool();
throw new IOException("OSF2F message decode failed: " + me.getMessage());
}
} else { // decode normal message
try {
Message msg = OSF2FMessageFactory.createOSF2FMessage(ref_buff);
messages_last_read.add(msg);
} catch (Throwable e) {
ref_buff.returnToPoolIfNotFree();
// maintain unexpected erorrs as such so they get logged
// later
if (e instanceof RuntimeException) {
throw ((RuntimeException) e);
}
throw new IOException("OSF2F message decode failed: " + e.getMessage());
}
}
reading_length_mode = true; // see if we've already read the
// next message's length
percent_complete = -1; // reset receive percentage
} else { // only partial received so far
percent_complete = (payload_buffer.position(SS) * 100) / message_length; // compute
// receive
// percentage
}
}
if (reading_length_mode && !destroyed) {
length_buffer.limit(SS, 4); // ensure proper buffer limit
prot_bytes_read += (pre_read_start_buffer == 1) ? length_buffer.position(SS)
- pre_read_start_position : length_buffer.position(SS);
if (!length_buffer.hasRemaining(SS)) { // done reading the length
reading_length_mode = false;
length_buffer.position(SS, 0);
message_length = length_buffer.getInt(SS);
length_buffer.position(SS, 0); // reset it for next length read
// System.out.println("decoded length: " + message_length);
if (message_length == HANDSHAKE_FAKE_LENGTH) { // handshake
// message
reading_handshake_message = true;
message_length = OSF2FHandshake.MESSAGE_LENGTH - 4; // restore
// 'real'
// length
payload_buffer = DirectByteBufferPool.getBuffer(DirectByteBuffer.AL_MSG,
message_length);
} else if (message_length == 0) { // keep-alive message
reading_length_mode = true;
last_received_was_keepalive = true;
try {
Message keep_alive = MessageManager.getSingleton().createMessage(
BTMessage.ID_BT_KEEP_ALIVE_BYTES, null, (byte) 1);
messages_last_read.add(keep_alive);
} catch (MessageException me) {
throw new IOException("BT message decode failed: " + me.getMessage());
}
} else if (message_length < MIN_MESSAGE_LENGTH
|| message_length > MAX_MESSAGE_LENGTH) {
throw new IOException("Invalid message length given for OS message decode: "
+ message_length);
} else { // normal message
payload_buffer = DirectByteBufferPool.getBuffer(
DirectByteBuffer.AL_MSG_BT_PAYLOAD, message_length);
}
}
}
protocol_bytes_last_read += prot_bytes_read;
data_bytes_last_read += data_bytes_read;
return prot_bytes_read + data_bytes_read;
}
public void pauseDecoding() {
is_paused = true;
}
public void resumeDecoding() {
is_paused = false;
}
}