// Commented for the Learning branch package com.limegroup.bittorrent; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import com.limegroup.gnutella.Assert; import com.limegroup.gnutella.BandwidthTracker; import com.limegroup.gnutella.io.ChannelReadObserver; import com.limegroup.gnutella.io.InterestReadChannel; import com.limegroup.bittorrent.statistics.BTMessageStat; import com.limegroup.bittorrent.statistics.BTMessageStatBytes; import com.limegroup.bittorrent.statistics.BandwidthStat; import com.limegroup.bittorrent.messages.BTMessage; import com.limegroup.bittorrent.messages.BadBTMessageException; /** * A BTMessageReader object slices data from a remote computer into BitTorrent messages, and gives them to BTConnection.processMessage(). * BTMesageReader joins a chain of readers in LimeWire's NIO design. * * The BTConnection constructor makes a new BTMessageReader, and saves it as _reader. * * BTMessageReader implements the ChannelReadObserver interface. * This means a BTMessageReader object has a channel it can read from, and the methods setReadChannel() and getReadChannel(). * This also means that NIO can tell a BTMessageReader when it should read by calling its handleRead() method. */ public class BTMessageReader implements ChannelReadObserver { /** 4, the size of the _in buffer before we make it exactly the size of the message we're reading. */ private static final int MIN_BUFFER_SIZE = 4; /** 32777 bytes, 32 KB, if we get a message bigger than this, we'll throw an exception. */ private static final int MAX_BUFFER_SIZE = 32 * 1024 + 9; /** A 4-byte array that can hold message data. */ private final byte[] _messageBuffer = new byte[MIN_BUFFER_SIZE]; /** The channel we read from. */ private InterestReadChannel _channel; /** The BTConnection object that made us, and that we are reading BitTorrent messages for. */ private final BTConnection _connection; /** * _in points to the ByteBuffer where we're putting the message we're currently reading. * adjustBuffer() allocates a new ByteBuffer for _in that is exactly the size of the message we're reading. * resetBuffer() points _in back at _messageBuffer, a 4-byte buffer that can hold just the length. (do) */ private ByteBuffer _in; /** The length of the message we're currently reading. */ private int _length; /** * A bandwidth tracker that can keep track of how fast we download data from the remote computer. * This is not the same things as a BitTorrent tracker. */ private SimpleBandwidthTracker _tracker; /** True when this object has been shut down, and won't be doing any more activity on the network. */ private volatile boolean shutdown; /** * Make a new BTMessageReader object, which will get data from the remote computer and slice it into BitTorrent messages. * Only the BTConnection constructor makes a new BTMessageReader object. * * @param connection A reference back up to the BTConnection object that is making this object */ public BTMessageReader(BTConnection connection) { // Set up buffers to read the next message resetBuffer(); // Save the given reference _connection = connection; // Make a new LimeWire SimpleBandwidthTracker object, which will keep track of how fast the remote computer is sending us data _tracker = new SimpleBandwidthTracker(); } /** * The "NIODispatch" thread calls handleRead() when this connection's BTMessageReader object can read more data from its channel. * handleRead() reads BitTorrent packet data from the remote computer. * The method slices it into individual packets, parses them into objects that extend BTMessage, and hands them off to this connection's processMessage() method. * This is the message slicer. */ public void handleRead() throws IOException { // Only do something if this connect has not been shut down if (shutdown) return; // Make sure the _in buffer has room for more data Assert.that(_in.hasRemaining(), "ByteBuffer full!"); // Make a variable for how many bytes we bring in each time the loop below runs int read = 0; // Loop until read is 0 no data right now, or -1 channel closed do { // Move data from the channel into the _in buffer read = _channel.read(_in); // Returns the number of bytes it moved if (read > 0) count(read); // Record that we got this many // If that filled the _in ByteBuffer if (!_in.hasRemaining()) { // We've read the entire message if (_in.position() == _length) { // The _in ByteBuffer's position reaches exactly over the length of the next BitTorrent message // Parse the message data at the start of the _in buffer into a message-type specific object that extends BTMessage BTMessage message = BTMessage.parseMessage( _in, // The ByteBuffer that has the message at the start of it, position and limit clip out empty space afterwards _length); // The length of the message at the start of _in // Give the BitTorrent message to this connection's processMessage() method _connection.processMessage(message); // Point _in back at _messageBuffer, and reset _length to -1 resetBuffer(); // Otherwise, we read just the first 4 bytes of the message } else { // Read the first 4 bytes to _length, and make _in exactly that long adjustBuffer(); } } // If we got something from our channel on that loop, loop again to get more } while (read > 0); } /** * Count that we received some data from the remote computer. * * @param read The number of bytes we read */ public void count(int read) { // Count it in statistics BandwidthStat.BITTORRENT_MESSAGE_DOWNSTREAM_BANDWIDTH.addData(read); // Have our SimpleBandwidthTracker count it _tracker.count(read); _tracker.measureBandwidth(); } /** * Get the SimpleBandwidthTracker this BTMessageReader uses to keep track of how fast we're receiving data from this remote computer. * This doesn't have anything to do with a BitTorrent tracker, the Web script that connects peers that have the same torrent. * * ManagedTorrent uses this to unchoke the hosts that are sending us lots of data. * * @return A reference to our SimpleBandwidthTracker object */ public BandwidthTracker getBandwidthTracker() { // Return our SimpleBandwidthTracker return _tracker; } /** * Read the length from the start of the _in buffer, set _length, and make _in exactly that long. * * Reads the length of the next message from the first 4 bytes of the _in ByteBuffer. * Sets _length to this length. * Replaces the _in ByteBuffer with one exactly as long as the length. * * Only handleRead() above calls this. * We've just read the first 4 bytes of a BitTorrent message. */ private void adjustBuffer() throws BadBTMessageException { // Change position and length from clipping around the empty space at the end of the buffer to the data before it _in.flip(); // Moves position to the start and length to where position was // Read the first 4 bytes in the _in ByteBuffer as a big endian int, this is the message length _in.order(ByteOrder.BIG_ENDIAN); _length = _in.getInt(); // Make sure the length is 0 or more, but not too big if (_length < 0 || _length > MAX_BUFFER_SIZE) throw new BadBTMessageException("bad message size " + _length); // If length is 0, this is a Keep Alive message if (_length == 0) { // Record that the remote computer sent us another Keep Alive message BTMessageStat.INCOMING_KEEP_ALIVE.incrementStat(); BTMessageStatBytes.INCOMING_KEEP_ALIVE.addData(4); // Get ready to read the next message _in.clear(); // In the _in ByteBuffer, move the position to the start and the limit to the end _length = -1; // We don't know the length of the next message yet // This message has a length of 1 or more bytes } else { // Replace the _in ByteBuffer with one exactly as long as the message _in = ByteBuffer.allocate(_length); } } /** * Set up buffers for reading the next message. * Points _in back at _messageBuffer, and sets _length to -1. * The BTMessageReader constructor calls this to set things up at the start. * handleRead() calls this each time it finishes reading a complete message from the remote computer. */ private void resetBuffer() { // Point _in at a ByteBuffer built around the _messageBuffer array _in = ByteBuffer.wrap(_messageBuffer); // Set _length to -1, we don't know how big the next message is yet _length = -1; } /** * NIO had an exception while sending or receiving data for this BTMessageReader object. * The "NIODispatch" thread will call this method if it gets an IOException when doing something for us. * * @param iox The IOException it got */ public void handleIOException(IOException iox) { // Pass it up to the BTConnection object we're reading messages for _connection.handleIOException(iox); } /** * Mark this BTMessageReader as shut down. */ public void shutdown() { // Only let one thread do this at a time synchronized(this) { // Set the shutdown flag to true if (shutdown) return; shutdown = true; } } /** * Give this BTMessageReader a channel it can get data from. * This is how it will receive BitTorrent message data from the remote computer. * * @param newChannel An InterestReadChannel this object can read data from */ public void setReadChannel(InterestReadChannel newChannel) { // Save the given channel _channel = newChannel; } /** * Get the channel this BTMessageReader gets data from. * This is how it receives BitTorrent message data from the remote computer. * * @return The InterestReadChannel this object reads data from */ public InterestReadChannel getReadChannel() { // Return the channel we've been using return _channel; } }