// Commented for the Learning branch package com.limegroup.bittorrent.handshaking; import java.io.IOException; import java.net.Socket; import java.nio.ByteBuffer; import java.util.Arrays; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.limegroup.bittorrent.BTConnectionFetcher; import com.limegroup.bittorrent.ManagedTorrent; import com.limegroup.bittorrent.TorrentLocation; import com.limegroup.gnutella.io.ConnectObserver; import com.limegroup.gnutella.io.NIOSocket; /** * Make an OutgoingBTHandshaker object to perform the BitTorrent handshake as soon as our connection attempt to a remote computer goes through. * * A BitTorrent handshake looks like this: * * S * BitTorrent protocol * EEEEEEEE * HHHHHHHHHHHHHHHHHHHH * PPPPPPPPPPPPPPPPPPPP * * S is 19, the length of the text that comes next. * "BitTorrent protocol" is 19 bytes of ASCII text. * E is the remote computer's 8 extension bytes. * H is the 20-byte SHA1 hash of the info section of the .torrent file. * P is the remote computer's peer ID. * * verifyIncoming() makes sure the remote computer's H matches the H we sent it. * * OutgoingBTHandshaker implements the ConnectObserver interface. * This means the "NIODispatch" thread can tell an OutgoingBTHandshaker object that its channel has connected. * The thread does this by calling the object's handleConnect() method. */ public class OutgoingBTHandshaker extends BTHandshaker implements ConnectObserver { /** A debugging log that saves lines of text as the program runs. */ private static final Log LOG = LogFactory.getLog(OutgoingBTHandshaker.class); /** The TorrentLocation object that represents the remote computer we're connecting to, and will shake hands with. */ private TorrentLocation loc; /** * Make a new OutgoingBTHandshaker object that will perform the BitTorrent handshake when Java makes our connection to a remote computer. * Only BTConnectionFetcher.fetchConnection() calls this constructor. * This constructor just saves the 2 given objects, it doesn't do anything else. * * @param loc A TorrentLocation object that represents the remote computer we're trying to connect to * @param torrent The ManagedTorrent object that represents the .torrent file we're making this connection for */ public OutgoingBTHandshaker(TorrentLocation loc, ManagedTorrent torrent) { // Save the given objects in this new one this.loc = loc; this.torrent = torrent; } /** * Get ready to shake hands with the remote computer. * * The "NIODispatch" thread calls an OutgoingBTHandshaker's handleConnect() method when the connection it initiated goes through. * Composes the handshake we'll send the remote computer, and makes buffers to read the remote computer's handshake. * Registers this object with NIO so it will call our handleRead() and handleWrite() methods when we can transfer data. * * @param socket The socket we connected. * socket looks like a java.net.Socket object, but it's actually a LimeWire NIOSocket object. */ public void handleConnect(Socket socket) throws IOException { // Make a note we established a connection to the remote computer's IP address if (LOG.isDebugEnabled()) LOG.debug("established connection to " + socket.getInetAddress()); // Save the NIOSocket object that we connected to the remote computer sock = (NIOSocket)socket; // Initialize the buffers and interests for NIO initOutgoingHandshake(); // Compose the handshake we'll send the remote computer initIncomingHandshake(); // Make the incomingHandshake buffers, where we'll put the data of the remote computer's handshake setWriteInterest(); // Register this object with NIO so it will call our handleWrite() method when we can send the remote computer data setReadInterest(); // Register this object with NIO so it will call our handleRead() method when the remote computer sends us data } /** * Make the empty incomingHandshake buffers, where we'll put the data of the remote computer's handshake. * * incomingHandshake[2] is a ByteBuffer object made by wrapping the extBytes byte array. * This means that when handleRead() writes data from the remote computer into the incomingHandshake[2] ByteBuffer, it will also go in the extBytes byte array. * incomingHandshake[4] does this for the peerID byte array the same way. */ protected void initIncomingHandshake() { // Make incomingHandshake an array of 5 ByteBuffer objects incomingHandshake = new ByteBuffer[5]; incomingHandshake[0] = ByteBuffer.allocate(1); // Make incomingHandshake[0] to hold 1 byte byte[] tmp = new byte[19]; incomingHandshake[1] = ByteBuffer.wrap(tmp); // Make incomingHandshake[1] to hold 19 bytes extBytes = new byte[8]; incomingHandshake[2] = ByteBuffer.wrap(extBytes); // Make incomingHandshake[2] to hold 8 bytes, linked to the extBytes byte array tmp = new byte[20]; incomingHandshake[3] = ByteBuffer.wrap(tmp); // Make incomingHandshake[3] to hold 20 bytes peerId = new byte[20]; incomingHandshake[4] = ByteBuffer.wrap(peerId); // Make incomingHandshake[4] to hold 20 bytes, linked to the peerId byte array } /** * Check the handshake the remote computer sent us after we connected to it. * * Makes sure the first byte has the length 19, and the 19 bytes "BitTorrent protocol" follow. * Makes sure the remote computer responded with the same torrent hash that we told it in our handshake. * * @return True if a complete part of the remote computer's handshake looks valid. * True if we don't have another new complete part yet. * False if there's a mistake in the handshake data the remote computer sent us. */ protected boolean verifyIncoming() { // Loop for each buffer we've filled in the incomingHandshake array of them since the last time handleRead() called verifyIncoming() for ( ; currentBufIndex < incomingHandshake.length && !incomingHandshake[currentBufIndex].hasRemaining(); currentBufIndex++) { ByteBuffer current = incomingHandshake[currentBufIndex]; // Verify the filled contents of incomingHandshake[0], the single byte that holds the length 19 switch(currentBufIndex) { case 0: // Make sure the first byte holds the number 19 current.flip(); // Move position to the start again if (current.get() != (byte)19) return false; // get() gets 1 byte, and moves position past it current.position(1); // Move the position back to the end again break; // Verify the filled contents of incomingHandshake[1], the 19 characters of ASCII text "BitTorrent protocol" case 1: // Make sure the text is "BitTorrent protocol" if (!Arrays.equals(current.array(), BTConnectionFetcher.BITTORRENT_PROTOCOL_BYTES)) return false; break; // Verify the filled contents of incomingHandshake[2], the remote computer's 8 extension bytes case 2: // No way to check it, it's valid break; // Verify the filled contents of incomingHandshake[3], the 20-byte hash of the torrent case 3: // Make sure the remote computer responded with the same torrent hash as we have if (!Arrays.equals(current.array(), torrent.getInfoHash())) return false; break; // Verify the filled contents of incomingHandshake[4], the remote computer's 20-byte peer ID case 4: // No way to check it, it's valid break; } } // Return true, we found no problems with the remote computer's handshake data return true; } /** * If our connection attempt to this remote computer fails, the "NIODispatch" thread will call this handleIOException() method. * Logs the failed connection attempt in the TorrentLocation object and adds it to one of the ManagedTorrent object's lists. * Tells this torrent's BTConnectionFetcher that this BTHandshaker object is done, and closes our socket to the remote computer. * * @param iox The IOException Java threw to the "NIODispatch" thread */ public void handleIOException(IOException iox) { // Make a note this happened if (LOG.isDebugEnabled()) LOG.debug("Connection failed: " + loc); // Log the failed connection attempt in the TorrentLocation object and add it to one of the ManagedTorrent object's lists loc.strike(); // Count that we tried to connect to this location, and couldn't if (!loc.isOut()) torrent.addEndpoint(loc); // Add this location to the torrent, we can try again later else torrent.addBadEndpoint(loc); // Add this location to the torrent's list of unreachable addresses, we've tried and failed too many times // Tell this torrent's BTConnectionFetcher that this BTHandshaker object is done, and close our socket to the remote computer super.handleIOException(iox); } }