import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.InputStream; import java.io.IOException; import java.io.OutputStream; import java.util.logging.Logger; /** * The Message Class handles the creation of all types of messages * necessary for BitTorrent to function properly. This class also handles * encoding and decoding of messages and creating the appropriate messages * accordingly. * * @author Deepak, Mike, Josh */ public class Message { /** The Constant log. */ private static final Logger log = Log2.getLogger(Message.class); /** The Constant keepAliveID. */ public static final byte keepAliveID = -1; /** The Constant chokeID. */ public static final byte chokeID = 0; /** The Constant unchokeID. */ public static final byte unchokeID = 1; /** The Constant interestedID. */ public static final byte interestedID = 2; /** The Constant uninterestedID. */ public static final byte uninterestedID = 3; /** The Constant haveID. */ public static final byte haveID = 4; /** The Constant bitfieldID. */ public static final byte bitfieldID = 5; /** The Constant requestID. */ public static final byte requestID = 6; /** The Constant pieceID. */ public static final byte pieceID = 7; /** The Constant cancelID. */ public static final byte cancelID = 8; /** The Constant portID. */ public static final byte portID = 9; /** The Constant KEEP_ALIVE. */ public static final Message KEEP_ALIVE = new Message(0, (byte) 255); /** The Constant CHOKE. */ public static final Message CHOKE = new Message(1, chokeID); /** The Constant UNCHOKE. */ public static final Message UNCHOKE = new Message(1, unchokeID); /** The Constant INTERESTED. */ public static final Message INTERESTED = new Message(1, interestedID); /** The Constant UNINTERESTED. */ public static final Message UNINTERESTED = new Message(1, uninterestedID); /** The id. */ protected final byte id; /** The length. */ protected final int length; /** * Instantiates a new message. * * @param length the length * @param id the id */ protected Message(final int length, final byte id) { this.id = id; this.length = length; } /** * Encode payload. * * @param dos the dos * @throws IOException Signals that an I/O exception has occurred. */ public void encodePayload(DataOutputStream dos) throws IOException { return; } /** * The Class Have. */ public static final class Have extends Message { /** The index. */ public final int index; /** * Instantiates a new have. * * @param index the index */ public Have(final int index) { super(5, haveID); this.index = index; } /* (non-Javadoc) * @see Message#encodePayload(java.io.DataOutputStream) */ public void encodePayload(DataOutputStream dos) throws IOException { dos.writeInt(this.index); return; } } /** * The Class Bitfield. */ public static final class Bitfield extends Message { /** The bitfield. */ public final byte[] bitfield; /** * Instantiates a new bitfield. * * @param bitfield the bitfield */ public Bitfield(final byte[] bitfield) { super(bitfield.length + 1, bitfieldID); this.bitfield = bitfield; } /* (non-Javadoc) * @see Message#encodePayload(java.io.DataOutputStream) */ public void encodePayload(DataOutputStream dos) throws IOException { dos.write(this.bitfield); return; } } /** * The Class Request. */ public static final class Request extends Message { /** The index. */ final int index; /** The start. */ final int start; /** The mlength. */ final int mlength; /** * Instantiates a new request. * * @param index the index * @param start the start * @param length the length */ public Request(final int index, final int start, final int length) { super(13, requestID); this.index = index; this.start = start; this.mlength = length; } /* (non-Javadoc) * @see Message#toString() */ public String toString() { return new String("Length: " + this.length + " ID: " + this.id + " Index: " + this.index + " Start: " + this.start + " Block: " + this.mlength); } /* (non-Javadoc) * @see Message#encodePayload(java.io.DataOutputStream) */ public void encodePayload(DataOutputStream dos) throws IOException { dos.writeInt(this.index); dos.writeInt(this.start); dos.writeInt(this.mlength); } } /** * The Class Piece. */ public static final class Piece extends Message { /** The index. */ final int index; /** The start. */ final int start; /** The block. */ final byte[] block; /* (non-Javadoc) * @see Message#toString() */ public String toString() { return new String("ID: " + this.id + " Length: " + this.length + " index: " + this.index); } /** * Instantiates a new piece. * * @param index the index * @param start the start * @param block the block */ public Piece(final int index, final int start, final byte[] block) { super(9 + block.length, pieceID); this.index = index; this.start = start; this.block = block; } /* (non-Javadoc) * @see Message#encodePayload(java.io.DataOutputStream) */ public void encodePayload(DataOutputStream dos) throws IOException { dos.writeInt(this.index); dos.writeInt(this.start); dos.write(this.block); //RUBTClient.addAmountUploaded(this.block.length); //double ratio = Tracker.downloaded / Tracker.uploaded; //RUBTClient.setShareRatio(ratio); } } /** * The Class Cancel. */ public static final class Cancel extends Message { /** The index. */ private final int index; /** The start. */ private final int start; /** The clength. */ private final int clength; /** * Instantiates a new cancel. * * @param index the index * @param start the start * @param length the length */ public Cancel(final int index, final int start, final int length) { super(13, cancelID); this.index = index; this.start = start; this.clength = length; } /* (non-Javadoc) * @see Message#encodePayload(java.io.DataOutputStream) */ public void encodePayload(DataOutputStream dos) throws IOException { dos.writeInt(this.index); dos.writeInt(this.start); dos.writeInt(this.clength); return; } } /** * Decode. * * Decodes a message and creates the new message * accordingly * * @param input the input * @return the message * @throws IOException Signals that an I/O exception has occurred. */ public static Message decode(final InputStream input) throws IOException { DataInputStream dataInput = new DataInputStream(input); int length = dataInput.readInt(); if (length == 0) { return KEEP_ALIVE; } byte id = dataInput.readByte(); switch (id) { case (chokeID): return CHOKE; case (unchokeID): return UNCHOKE; case (interestedID): return INTERESTED; case (uninterestedID): return UNINTERESTED; case (haveID): int index = dataInput.readInt(); return new Have(index); case (bitfieldID): byte[] bitfield = new byte[length - 1]; dataInput.readFully(bitfield); return new Bitfield(bitfield); case (pieceID): int ind = dataInput.readInt(); int start = dataInput.readInt(); byte[] block = new byte[length - 9]; dataInput.readFully(block); return new Piece(ind, start, block); case (requestID): int in = dataInput.readInt(); int begin = dataInput.readInt(); length = dataInput.readInt(); return new Request(in, begin, length); } return null; } /** * Encode. * * Encodes the message we want to send * * @param message the message * @param output the output * @throws IOException Signals that an I/O exception has occurred. */ public static void encode(final Message message, final OutputStream output) throws IOException { log.info("Encoding message " + message); if (message != null) { { DataOutputStream dos = new DataOutputStream(output); dos.writeInt(message.length); if (message.length > 0) { dos.write(message.id); message.encodePayload(dos); } dos.flush(); } } } /** The Constant TYPE_NAMES. */ private static final String[] TYPE_NAMES = new String[]{"Choke", "Unchoke", "Interested", "Uninterested", "Have", "Bitfield", "Request", "Piece", "Cancel", "Port"}; /* (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { if(this.length == 0){ return "Keep-Alive"; } return TYPE_NAMES[this.id]; } }