package org.torrent.internal.protocol;
import java.nio.ByteBuffer;
import java.util.LinkedList;
import java.util.Queue;
import org.torrent.internal.peer.connection.SocketConnection.BittorrentType;
import org.torrent.internal.protocol.message.BittorrentMessage;
import org.torrent.internal.protocol.message.RawMessage;
import org.torrent.internal.protocol.realtime.BTTransformRealtime;
import org.torrent.internal.protocol.realtime.BTTransformRealtimeImpl;
import org.torrent.internal.util.Validator;
public class BittorrentDecoder {
private enum State {
HANDSHAKE_A, HANDSHAKE_B, LENGTH, MESSAGE;
}
private static final int MAX_MESSAGE_SIZE = 128 * 1024;
private final ByteBuffer msgLen = ByteBuffer.allocate(4);
private State state = State.HANDSHAKE_A;
private Queue<BittorrentMessage> messages = new LinkedList<BittorrentMessage>();
private ByteBuffer buffer;
private BTTransform protocol;
public BittorrentDecoder(BittorrentType type) {
if(type == BittorrentType.REALTIME)
this.protocol = new BTTransformRealtimeImpl();
else
this.protocol = new BTTransformImpl();
// this(new BTTransformImpl());
buffer = ByteBuffer.wrap(new byte[protocol.getHandShakeASize()]);
}
// public BittorrentDecoder(BTTransform protocol) {
// this.protocol = protocol;
// buffer = ByteBuffer.wrap(new byte[protocol.getHandShakeASize()]);
// }
public void update(byte[] data) throws BittorrentMessageDecodingException {
Validator.notNull(data, "Data is null!");
update(data, 0, data.length);
}
public void update(byte[] data, int off, int len)
throws BittorrentMessageDecodingException {
Validator.notNull(data, "Data is null!");
Validator.isTrue(off >= 0 && off + len <= data.length && len > 0,
"Invalid offset/length: " + off + "/" + len);
while (len > 0) {
int t = Math.min(len, buffer.remaining());
buffer.put(data, off, t);
off += t;
len -= t;
if (!buffer.hasRemaining()) {
buffer.flip();
handleBuffer();
assert buffer.hasRemaining() || buffer.limit() == 4 : "Last message: "
+ ((LinkedList<BittorrentMessage>) messages).getLast();
}
}
}
private void handleBuffer() throws BittorrentMessageDecodingException {
int length;
switch (state) {
case HANDSHAKE_A:
messages.add(protocol.decodeHandshakeA(buffer));
buffer = ByteBuffer.allocate(protocol.getHandShakeBSize());
state = State.HANDSHAKE_B;
break;
case HANDSHAKE_B:
messages.add(protocol.decodeHandshakeB(buffer));
buffer = msgLen;
buffer.clear();
state = State.LENGTH;
break;
case LENGTH:
length = buffer.getInt(0);
if (length > MAX_MESSAGE_SIZE) {
throw new BittorrentMessageDecodingException(
"Message exceeds maximum size: " + length);
}
if (length == 0) {
messages.add(protocol.decodeKeepAlive(buffer));
buffer.clear();
} else {
buffer = ByteBuffer.wrap(new byte[4 + length]);
buffer.putInt(length);
state = State.MESSAGE;
}
break;
case MESSAGE:
length = buffer.getInt(0);
if (buffer.limit() < 5) {
throw new BittorrentMessageDecodingException(
"Expected length to be " + (length + 4) + " but was "
+ buffer.limit());
}
switch (buffer.get(4)) {
case 0:
messages.add(protocol.decodeChoke(buffer));
break;
case 1:
messages.add(protocol.decodeUnChoke(buffer));
break;
case 2:
messages.add(protocol.decodeInterested(buffer));
break;
case 3:
messages.add(protocol.decodeNotInterested(buffer));
break;
case 4:
messages.add(protocol.decodeHave(buffer));
break;
case 5:
messages.add(protocol.decodeBitField(buffer));
break;
case 6:
messages.add(protocol.decodeRequest(buffer));
break;
case 7:
messages.add(protocol.decodePiece(buffer));
break;
case 8:
messages.add(protocol.decodeCancel(buffer));
break;
case 9:
messages.add(protocol.decodePort(buffer));
break;
// case 10:
// messages.add(protocol.decodeDontHave(buffer));
// break;
// case 11:
// messages.add(protocol.decodeWinUpdate(buffer));
// break;
default:
messages.add(new RawMessage(buffer));
break;
}
buffer = msgLen;
buffer.clear();
state = State.LENGTH;
break;
}
}
public BittorrentMessage nextMessage() {
if (messages.isEmpty()) {
return null;
}
return messages.remove();
}
}