// Commented for the Learning branch
package com.limegroup.bittorrent;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.limegroup.bittorrent.handshaking.BTHandshaker;
import com.limegroup.bittorrent.handshaking.OutgoingBTHandshaker;
import com.limegroup.gnutella.Constants;
import com.limegroup.gnutella.ErrorService;
import com.limegroup.gnutella.io.Shutdownable;
import com.limegroup.gnutella.util.Sockets;
/**
* A BTConnectionFetcher keeps a list of the 5 IncomingBTHandshaker and OutgoingBTHandshaker objects doing the handshake with remote computers for this torrent.
*
* A ManagedTorrent has a BTConnectionFetcher which initiates new TCP socket connections to remote computers that are sharing the same torrent.
* The BTConnectionFetcher keeps a list, named fetchers, of OutgoingBTHandshaker and IncomingBTHandshaker objects.
* OutgoingBTHandshaker objects perform the BitTorrent handshake with a remote computer once our connection to it goes through.
* IncomingBTHandshaker objects complete the BitTorrent handshake with a remote computer that connected to us.
*
* The fetch() method loops to fill our fetchers list with up to 5 OutgoingBTHandshaker objects.
* Each of these objects will wait for our connection to a new remote computer to go through, and then perform the BitTorrent handshake with it.
*
* ManagedTorrent.initializeTorrent() calls the BTConnectionFetcher constructor.
* It saves the object in as _connectionFetcher.
*/
public class BTConnectionFetcher {
/** A debugging log we can write lines of text to as the program runs. */
private static final Log LOG = LogFactory.getLog(BTConnectionFetcher.class);
/** "BitTorrent protocol", the BitTorrent handshake greeting as a String. */
private static final String BITTORRENT_PROTOCOL = "BitTorrent protocol";
/** The ASCII text "BitTorrent protocol" as a 19-byte array. */
public static byte[] BITTORRENT_PROTOCOL_BYTES;
static {
try {
BITTORRENT_PROTOCOL_BYTES = BITTORRENT_PROTOCOL.getBytes(Constants.ASCII_ENCODING);
} catch (UnsupportedEncodingException e) {
ErrorService.error(e);
}
}
/**
* The extension bytes that describe which BitTorrent features we support.
* 8 bytes that are all 0 for a basic BitTorrent program.
* We put a 2 in the last byte to indicate support for. (do)
*/
static final byte[] EXTENSION_BYTES = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 };
/** 5, We won't try to connect to more than 5 computers at once. */
private static final int MAX_CONNECTORS = 5;
/**
* The list of OutgoingBTHandshaker and IncomingBTHandshaker objects that this BTConnectionFetcher has trying to connect to remote computers.
* At most, there will be 5.
*
* fetchers is a HashSet of OutgoingBTHandshaker objects.
* A HashSet is a list that blocks duplicates.
*/
final Set fetchers = Collections.synchronizedSet(new HashSet());
/**
* The torrent this BTConnectionFetcher object is fetching connections for.
*
* This is a link back to the ManagedTorrent object.
* managedTorrent._connectionFetcher is this BTConnectionFetcher.
* this._torrent is our link back up to the ManagedTorrent.
*/
final ManagedTorrent _torrent;
/**
* The handshake we'll send remote computers we have connections with to download and share the torrent.
* _handshake is a ByteBuffer with 68 bytes of data in it:
*
* S A byte that holds the length of the text that follows, 19
* BitTorrent protocol ASCII text that identifies this as the BitTorrent protocol
* 00000002 8 bytes that tell which advanced BitTorrent features we support, they're all 0 except for the last one, where the 2 bit is set
* HHHHHHHHHHHHHHHHHHHH The hash of the info section of the .torrent file, tells the remote computer which torrent we're interested in sharing with it
* LIMEguidguidguidguid Our peer ID, our vendor code "LIME" followed by our GUID.
*/
final ByteBuffer _handshake;
/**
* True when this object is shut down.
*/
private volatile boolean shutdown;
/**
* Make a new BTConnectionFetcher that will try to connect to remote computers downloading and uploading the same torrent we are.
*
* Only ManagedTorrent.initializeTorrent() calls this constructor.
* It saves the new BTConnectionFetcher object in its _connectionFetcher member variable.
*
* @param torrent The torrent we're downloading
* @param peerId Our peer ID like "LIMEguidguidguidguid", with the 16-byte GUID after the ASCII vendor code "LIME"
*/
BTConnectionFetcher(ManagedTorrent torrent, byte[] peerId) {
// Save the torrent we're making this BTConnectionFetcher for
_torrent = torrent;
// Compose the handshake we'll send remote computers connected to us to share the torrent
ByteBuffer handshake = ByteBuffer.allocate(68); // Our handshake will be 68 bytes of data
handshake.put((byte)BITTORRENT_PROTOCOL.length()); // S The BitTorrent handshake starts with the number 19 in a single byte
handshake.put(BITTORRENT_PROTOCOL_BYTES); // BitTorrent protocol After that are the 19 bytes of ASCII text "BitTorrent protocol"
handshake.put(EXTENSION_BYTES); // EEEEEEEE The 8 bytes that describe which advanced BitTorrent features we support
handshake.put(_torrent.getInfoHash()); // HHHHHHHHHHHHHHHHHHHH The 20-byte SHA1 hash of the info portion of the .torrent file
handshake.put(peerId); // PPPPPPPPPPPPPPPPPPPP Our 20-byte peer ID, that has our vendor code LIME and our 16-byte GUID
handshake.flip(); // Move position back to the start, getting the buffer ready for reading
_handshake = handshake.asReadOnlyBuffer(); // Save our handshake data in this new BTConnectionFetcher object
}
/**
* Loop to fill our fetchers list with up to 5 IncomingBTHandshake and OutgoingBTHandshake objects.
* If we have less than 5, make new OutgoingBTHandshake objects that will connect to remote computers and do the BitTorrent handshake with them.
*/
public synchronized void fetch() {
// Don't do anything if we're shut down
if (shutdown) return;
// Check the ManagedTorrent's shouldStop() method, and call stop() if necessary (do)
if (_torrent.shouldStop()) {
_torrent.stop();
return;
}
// Loop to try fill our fetchers list with up to 5 BTConnectionFetcher objects, each of which will try to connect to a remote computer that has our torrent
while (
!_torrent.hasStopped() && // No one has set _stopped to true in the ManagedTorrent object yet, and
fetchers.size() < MAX_CONNECTORS && // We're trying to open connections and do the handshake with fewer than 5 new remote computers right now, and
_torrent.needsMoreConnections() && // Our torrent needs more connections, and
_torrent.hasNonBusyLocations()) { // Not all of our torrent's locations are busy (do)
// Connect to a new remote computer that's sharing the torrent, and perform the handshake with it.
fetchConnection();
if (LOG.isDebugEnabled()) LOG.debug("started connection fetcher: " + fetchers.size());
}
}
/**
* Connect to a new remote computer that's sharing the torrent, and perform the handshake with it.
*
* Asks _torrent for the IP address and port number of a remote computer that's sharing it too.
* Makes a new OutgoingBTHandshaker object that will perform the BitTorrent handshake as soon as we connect to the remote computer.
* Calls Sockets.connect() to open a new TCP socket connection to the remote computer.
* When the "NIODispatch" thread makes the connection, it will call the OutgoingBTHandshaker's handleConnect() method.
*/
private void fetchConnection() {
// Get the address of a remote compuer that has the same torrent as we do
TorrentLocation ep = _torrent.getTorrentLocation();
if (ep == null) {
if (LOG.isDebugEnabled()) LOG.debug("no hosts to connect to");
return;
}
// Make an OutgoingBTHandshaker object which will perform the BitTorrent handshake as soon as our connection attempt to ep goes through
OutgoingBTHandshaker connector = new OutgoingBTHandshaker(ep, _torrent);
fetchers.add(connector); // Add it to our list
try {
// Open a new TCP socket connection to the remote computer
Sockets.connect(
ep.getAddress(), // The IP address and port number of the remote computer
ep.getPort(),
Constants.TIMEOUT, // Give up after 8 seconds
connector); // The "NIODispatch" thread will call connector.handleConnect() when the connection goes through
} catch (IOException impossible) { ErrorService.error(impossible); }
}
/**
* Make this BTConnectionFetcher object stop all its network activity.
*
* Loops through our fetchers list of OutgoingBTHandshaker objects, calling shutdown() on each one.
*/
synchronized void shutdown() {
// Only do this once, and record that it has been done
if (shutdown) return;
shutdown = true;
// Only let one thread access the fetchers list at a time
synchronized (fetchers) {
// Loop for each OutgoingBTHandshaker in our fetchers list
List conns = new ArrayList(fetchers); // Make a copy of the fetchers list because connector.shutdown() will remove it from fetchers
for (Iterator iter = conns.iterator(); iter.hasNext(); ) {
Shutdownable connector = (Shutdownable)iter.next();
// Have it stop all its network activity
connector.shutdown();
}
}
}
/**
* Get the handshake we'll send remote computers we have connections with to download and share the torrent.
* Returns a ByteBuffer with 68 bytes of data in it:
*
* S A byte that holds the length of the text that follows, 19.
* BitTorrent protocol ASCII text that identifies this as the BitTorrent protocol.
* 00000002 8 bytes that tell which advanced BitTorrent features we support, they're all 0 except for the last one, where the 2 bit is set.
* HHHHHHHHHHHHHHHHHHHH The hash of the info section of the .torrent file, tells the remote computer which torrent we're interested in sharing with it.
* LIMEguidguidguidguid Our peer ID, our vendor code "LIME" followed by our GUID.
*
* @return A ByteBuffer with the 68 bytes of our handshake data
*/
public ByteBuffer getOutgoingHandshake() {
// Return a copy of the ByteBuffer so the caller can do whatever they want with it
return _handshake.duplicate();
}
/**
* Add the given IncomingBTHandshaker to this BTConnectionFetcher's list of them.
* Only IncomingBTHandshaker.verifyIncoming() calls this method.
* If a remote computer contacts us, this is how its IncomingBTHandshaker objects gets in our list.
*
* @param shaker An IncomingBTHandshaker object from a remote computer that contacted us
*/
public void handshakerStarted(BTHandshaker shaker) {
// Add the given IncomingBTHandshaker to our fetchers list
fetchers.add(shaker);
}
/**
* Remove the given BTHandshaker from our fetchers list.
* Also calls fetch() to get more if we still can and don't have enough.
*
* @param shaker The IncomingBTHandshaker or OutgoingBTHandshaker object to remove
*/
public void handshakerDone(BTHandshaker shaker) {
// Remove the given object from the featchers list, and call fetch() to get more connections if necessary
if (fetchers.remove(shaker)) fetch();
}
}