// Commented for the Learning branch package com.limegroup.bittorrent.messages; import java.nio.ByteBuffer; import com.limegroup.bittorrent.statistics.BTMessageStat; import com.limegroup.bittorrent.statistics.BTMessageStatBytes; /** * BTMessage is the base class for objects like BTChoke and BTHave that represent BitTorrent messages. * * Call the static method BTMessage.parseMessage(buffer, length) with a buffer that contains a BitTorrent message. * The static parseMessage() method will hand it off to a message type-specific method, like BTChoke.readMessage(). * * A BitTorrent message looks like this: * * LLLLT * * The first 4 bytes, LLLL, are an int in big endian byte order. * They tell the length of the message that follows them. * The first byte of the message body, T, tells what type of message it is. * * A Keep Alive message is a special case. * The length LLLL is 0, and no data follows in accordance with that length. * BitTorrent programs send Keep Alive messages to keep a quiet socket from closing. */ public abstract class BTMessage { /** 0xff, the byte that identifies a BitTorrent Keep Alive message. */ public static final byte KEEP_ALIVE = (byte)0xFF; /** 0x00, the byte that identifies a BitTorrent Choke message. */ public static final byte CHOKE = 0x00; /** 0x01, the byte that identifies a BitTorrent Unchoke message. */ public static final byte UNCHOKE = 0x01; /** 0x02, the byte that identifies a BitTorrent Interested message. */ public static final byte INTERESTED = 0x02; /** 0x03, the byte that identifies a BitTorrent Not Interested message. */ public static final byte NOT_INTERESTED = 0x03; /** 0x04, the byte that identifies a BitTorrent Have message. */ public static final byte HAVE = 0x04; /** 0x05, the byte that identifies a BitTorrent Bitfield message. */ public static final byte BITFIELD = 0x05; /** 0x06, the byte that identifies a BitTorrent Request message. */ public static final byte REQUEST = 0x06; /** 0x07, the byte that identifies a BitTorrent Piece message. */ public static final byte PIECE = 0x07; /** 0x08, the byte that identifies a BitTorrent Cancel message. */ public static final byte CANCEL = 0x08; /** * A ByteBuffer with no length that can hold no data. * BitTorrent message objects that don't have payloads have getPayload() methods that return EMPTY_PAYLOAD. */ static final ByteBuffer EMPTY_PAYLOAD; // Setup EMPTY_PAYLOAD to point at a 0-length ByteBuffer static { EMPTY_PAYLOAD = ByteBuffer.allocate(0); } /** The byte that identifies what kind of BitTorrent message this is. */ private final byte _type; /** * Make a new BTMessage object to represent a BitTorrent message. * * @param type The byte that identifies what type of BitTorrent message this is, like 0x00 Choke or 0x02 Interested */ BTMessage(byte type) { // Save the given type byte in this new BTMessage object _type = type; } /** * Find out what kind of BitTorrent message this is. * Reads the type byte, the first byte of the payload, which comes after the 4 size bytes. * * @return The type byte, like 0x01 Choke or 0x02 Interested */ public byte getType() { // Return the type byte return _type; } /** * Get the data of this BitTorrent message's payload, without the message length. (do) * Classes that extend BTMessage must have getPayload() methods. * * @return The message payload data in a ByteBuffer object */ public abstract ByteBuffer getPayload(); /** * Read the data of a BitTorrent message from a given ByteBuffer, parsing it into an object that extends BTMessage, and processing it. * Only BTMessageReader.handleRead() calls this method. * * @param in A ByteBuffer that has the data of a BitTorrent message from a remote computer. * parseMessage() will start reading at in.position, and not go past in.length. * It will move in.position forward past the data of the message it reads. (do) * It will also compact the buffer, moving the data to the start. (do) * @param length The length of the message. * The first 4 bytes in the in ByteBuffer contain this length number. * @return An object that extends BTMessage and represents the BitTorrent message we read. */ public static BTMessage parseMessage(ByteBuffer in, int length) throws BadBTMessageException { /* * in case this is a keep alive message, length is not necessarily > 0 * the reason we do not return here is that we reset the buffer below. */ // Clip out the data in the in ByteBuffer in.flip(); // Move position to the start and limit to where position was // By default, set type for a Keep Alive message, which has no length byte type = KEEP_ALIVE; // If length is bigger than 0, this isn't a Keep Alive message, look in the message data to find out what kind of message it is if (length > 0) { // Read the first byte from the message payload, which tells the type type = in.get(); } // Sort by what type of message it is switch (type) { // Keep Alive message case KEEP_ALIVE: // Record in statistics BTMessageStat.INCOMING_KEEP_ALIVE.incrementStat(); BTMessageStatBytes.INCOMING_KEEP_ALIVE.addData(4); // A Keep Alive message takes up 4 bytes // It's not necessary to do anything else return null; // Choke message case CHOKE: // Record in statistics BTMessageStat.INCOMING_CHOKE.incrementStat(); BTMessageStatBytes.INCOMING_CHOKE.addData(5); // A Choke message takes up 5 bytes // Give the message data to a method specific to its type return BTChoke.readMessage(in); // Unchoke message case UNCHOKE: // Record in statistics BTMessageStat.INCOMING_UNCHOKE.incrementStat(); BTMessageStatBytes.INCOMING_UNCHOKE.addData(5); // An Unchoke message takes up 5 bytes // Give the message data to a method specific to its type return BTUnchoke.readMessage(in); // Interested message case INTERESTED: // Record in statistics BTMessageStat.INCOMING_INTERESTED.incrementStat(); BTMessageStatBytes.INCOMING_INTERESTED.addData(5); // An Interested message takes up 5 bytes // Give the message data to a method specific to its type return BTInterested.readMessage(in); // Not Interested message case NOT_INTERESTED: // Record in statistics BTMessageStat.INCOMING_NOT_INTERESTED.incrementStat(); BTMessageStatBytes.INCOMING_NOT_INTERESTED.addData(5); // A Not Interested message takes up 5 bytes // Give the message data to a method specific to its type return BTNotInterested.readMessage(in); // Have message case HAVE: // Record in statistics BTMessageStat.INCOMING_HAVE.incrementStat(); BTMessageStatBytes.INCOMING_HAVE.addData(9); // A Have message takes up 9 bytes // Give the message data to a method specific to its type return BTHave.readMessage(in); // Bitfield message case BITFIELD: // Record in statistics BTMessageStat.INCOMING_BITFIELD.incrementStat(); BTMessageStatBytes.INCOMING_BITFIELD.addData(5 + in.remaining()); // Pass the size of the Bitfield message to addData() // Give the message data to a method specific to its type return BTBitField.readMessage(in); // Request message case REQUEST: // Record in statistics BTMessageStat.INCOMING_REQUEST.incrementStat(); BTMessageStatBytes.INCOMING_REQUEST.addData(17); // A Request message takes up 14 bytes TODO: why is this 17? // Give the message data to a method specific to its type return BTRequest.readMessage(in); // Piece message case PIECE: // Record in statistics BTMessageStat.INCOMING_PIECE.incrementStat(); BTMessageStatBytes.INCOMING_PIECE.addData(5 + in.remaining()); // Pass the size of the piece message to addData() // Give the message data to a method specific to its type return BTPiece.readMessage(in); // Cancel message case CANCEL: // Record in statistics BTMessageStat.INCOMING_CANCEL.incrementStat(); BTMessageStatBytes.INCOMING_CANCEL.addData(17); // A Cancel message takes up 14 bytes TODO: why is this 17? // Give the message data to a method specific to its type return BTCancel.readMessage(in); // The type byte has some other value default: // We only know what to do with the message types outlined above throw new BadBTMessageException("unknown message, type " + type); } } }