// Commented for the Learning branch
package com.limegroup.bittorrent.handshaking;
import java.io.IOException;
import java.nio.ByteBuffer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.limegroup.bittorrent.BTConnection;
import com.limegroup.bittorrent.ManagedTorrent;
import com.limegroup.bittorrent.TorrentLocation;
import com.limegroup.gnutella.io.ChannelReadObserver;
import com.limegroup.gnutella.io.ChannelWriter;
import com.limegroup.gnutella.io.InterestReadChannel;
import com.limegroup.gnutella.io.InterestScatteringByteChannel;
import com.limegroup.gnutella.io.InterestWriteChannel;
import com.limegroup.gnutella.io.NIOSocket;
/**
* BTHandshaker is the base class for IncomingBTHandshaker and OutgoingBTHandshaker.
* It has the common code to exchange data with a remote computer using LimeWire's NIO system.
*
* The program never makes a BTHandshaker object.
* Instead, it makes IncomingBTHandshaker and OutgoingBTHandshaker objects, which both extend this BTHandshaker class.
*
* BTHandshaker implements the ChannelWriter interface.
* This means a BTHandshaker object has a channel it writes to, and has the methods setWriteChannel and getWriteChannel().
* It also means the "NIODispatch" thread can tell a BTHandshaker object to get data, the thread does this by calling handleWrite().
*
* BTHandshaker also implements the ChannelReadObserver interface.
* This means a BTHandshaker object has a channel it can read from, and has the methods setReadChannel() and getReadChannel().
* It also means the "NIODispatch" thread can tell a BTHandshaker object to read data, the thread does this by calling handleRead().
*
* 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.
*
* The member variable extBytes holds E.
* The member variable peerId holds P.
*/
public abstract class BTHandshaker implements ChannelWriter, ChannelReadObserver {
/** A debugging log we can write lines of text to as the program runs. */
private static final Log LOG = LogFactory.getLog(BTHandshaker.class);
/**
* The object that represents the .torrent file we're shaking hands with a remote computer to get.
* A ManagedTorrent is an object that represents a .torrent file we're downloading and uploading.
*/
protected ManagedTorrent torrent;
/**
* The data of our handshake for the remote computer.
*/
protected ByteBuffer outgoingHandshake;
/**
* An array of ByteBuffers that hold the data of the handshake the remote computer sends us, split into different parts.
*
* incomingHandshake is an array of ByteBuffer objects.
* The ScatteringByteChannel.read() method can move data from a channel into an array of ByteBuffers like this one.
* When one ByteBuffer fills up, it starts putting data into the next one.
* Determine if they're all full by seeing if the position in the last one has moved to its limit.
*
* OutgoingBTHandshaker.initIncomingHandshake() makes 5 ByteBuffer objects for all 5 lines of the BitTorrent handshake.
* IncomingBTHandshaker.initIncomingHandshake() only makes 4, because the Acceptor has already the 19 byte and the text "BitTorrent ".
*
* The handleRead() method here moves data from our channel into these buffers.
*/
protected ByteBuffer[] incomingHandshake;
/**
* The index of the buffer we're on in incomingHandshake.
* For instance, if currentBufIndex is 1, we're in incomingHandshake[1], the 19-byte buffer that will hold the greeting "BitTorrent protocol".
*/
protected int currentBufIndex;
/**
* The object we give data to send it to the remote computer.
* This is the next object in a chain of writers that leads to the remote computer.
* NIO will call our handleWrite() method when we can send data.
* We'll send data by calling writeChannel.write(data).
*/
protected InterestWriteChannel writeChannel;
/**
* The object we get data from the remote computer from.
* This is the next object in a chain of readers that leads to the remote computer.
* NIO will call our handleRead() method when we can get data.
* We'll get data by calling readChannel.read(destinationBuffer).
*/
protected InterestScatteringByteChannel readChannel;
/** The NIOSocket object which connects us to the remote computer. */
protected NIOSocket sock;
/**
* The remote computer has sent its entire handshake.
*
* When the remote computer has sent us its entire BitTorrent handshake, handleRead() sets incomingDone to true.
* This also means that we've read the 64-byte BitTorrent handshake from the channel.
* The next data inside it is the start of the first BitTorrent packet.
*/
protected boolean incomingDone;
/**
* We've exchanged complete handshakes with the remote computer.
*
* When the remote computer has sent us its entire BitTorrent handshake, and we've sent it ours, tryToFinishHandshakes() will set finishingHandshake to true.
*/
protected boolean finishingHandshakes;
/**
* True when the shutdown() method has been called, and this object is being shut down.
*/
private volatile boolean shutdown;
/**
* The remote computer's extension bytes.
* These are 8 bytes that come after the text "BitTorrent protocol".
*
* These bytes tell what extensions to the BitTorrent protocol the remote computer understands.
* Many BitTorrent clients will pass all 0s for these bytes.
*/
protected byte[] extBytes;
/**
* The remote computer's peer ID.
* These are the last 20 bytes of the BitTorrent handshake.
*
* The first 4 bytes of the peer ID are the vendor code, like "LIME".
*/
protected byte[] peerId;
/**
* Read handshake data the remote computer sent us.
* If we're done with the handshake, make objects to represent this new connection and register them with the ManagedTorrent.
*
* The "NIODispatch" thread calls handleRead() when we can read from our readChannel.
* The data we get from it will be the next part of the remote computer's handshake with us.
*/
public void handleRead() throws IOException {
// If we have been shut down, don't do anything
if (shutdown) return;
// Move data from readChannel into the incomingHandshake buffers
long read = 0;
while (
// Move data from readChannel into the incomingHandshake buffers, when one fills, data will go into the next one
(read = readChannel.read(incomingHandshake)) > 0 && // Get read, the number of bytes it moved, if it moved some data, go to the next line
// If the last ByteBuffer in the incomingHandshake array of them isn't full yet, loop to keep reading
incomingHandshake[incomingHandshake.length - 1].hasRemaining()) ; // There's no code in this while loop
// Make sure the handshake the remote computer sent us is valid
if (read == -1 || // The channel is closed, or
!verifyIncoming()) { // Part of the remote computer's handshake is bad
// Shut down this BTHandshaker and leave
if (LOG.isDebugEnabled()) LOG.debug("bad incoming handshake on element " + currentBufIndex + " or channel closed " + read);
shutdown();
return;
}
// If the remote computer has sent us its entire handshake
if (!incomingHandshake[incomingHandshake.length - 1].hasRemaining()) { // If the last ByteBuffer in the incomingHandshake array is filled with data
// We're done reading handshake data from the remote computer
if (LOG.isDebugEnabled()) LOG.debug("incoming handshake finished " + sock.getInetAddress());
incomingDone = true;
}
// If we're done with the handshake, make objects to represent this new connection and register them with the ManagedTorrent
tryToFinishHandshakes();
}
/**
* IncomingBTHandshaker and OutgoingBTHandshaker have verifyIncoming() methods that check the handshake data the remote computer sent us.
*
* @return True if the remote computer's handshake looks OK, false if there is a problem with it
*/
protected abstract boolean verifyIncoming();
/**
* Send our handshake to the remote computer.
* If we're done with the handshake, make objects to represent this new connection and register them with the ManagedTorrent.
*
* The "NIODispatch" thread calls handleWrite() when we can write to our channel.
* This is when and how we send data to the remote computer.
*/
public boolean handleWrite() throws IOException {
// If we have been shut down, don't do anything
if (shutdown) return false;
// Send the remote computer our handshake
int wrote = 0; // The number of bytes write() sent
while ((wrote = writeChannel.write(outgoingHandshake)) > 0 && outgoingHandshake.hasRemaining()); // Loop until we fill the channel or run out of handshake
// If we sent our entire handshake, tell the channel we write to that we're not interested in giving it any more data
if (!outgoingHandshake.hasRemaining()) writeChannel.interest(this, false);
// If we're done with the handshake, make objects to represent this new connection and register them with the ManagedTorrent
tryToFinishHandshakes();
/*
* this falls through to SocketAdapter which ignores it.
*/
// It doesn't matter what this handleWrite() method returns
return true; // A handleWrite() method returns true to report we filled the channel, and still have more data to write
}
/**
* IncomingBTHandshaker and OutgoingBTHandshaker have initIncomingHandshake() methods that make buffers for incomingHandshake.
*/
protected abstract void initIncomingHandshake();
/**
* Compose the handshake we'll send to the remote computer.
* Has the torrent's connection fetcher put it together.
* Saves it in the outgoingHandshake ByteBuffer.
*/
protected void initOutgoingHandshake() {
// Have the ManagedTorrent object's BTConnectionFetcher compose our handshake for the remote computer
outgoingHandshake = torrent.getFetcher().getOutgoingHandshake();
}
/**
* Register this object with NIO so it will call our handleRead() method when the remote computer sends us data.
*/
protected final void setReadInterest() {
// Tell the NIOSocket object connected to the remote computer we're the object it should forward NIO's commands to read to
sock.setReadObserver(this);
// Tell the channel we get data from to tell us when it has data for us
readChannel.interest(true); // It will do this by calling our handleRead() method
}
/**
* Register this object with NIO so it will call our handleWrite() method when we can send the remote computer data.
*/
protected final void setWriteInterest() {
// Tell the NIOSocket object connected to the remote computer we're the object it should forward NIO's commands to write to
sock.setWriteObserver(this);
// Tell the channel we send data into to tell us when we can do that
writeChannel.interest(this, true); // It will tell us when by calling our handleWrite() method
}
/**
* Sets up this new connection to a remote BitTorrent computer.
*
* If we're done exchanging handshake data with the remote computer, does the following things.
* Sets finishingHandshakes to true.
* Makes TorrentLocation and BTConnection objects to represent the remote computer.
* Adds them to the ManagedTorrent object that represents the .torrent file this is all for.
* Tells the .torrent's BTConnectionFetcher that we're done.
*/
private void tryToFinishHandshakes() {
// If this method has already run, or this object is shut down, leave without doing anything
if (finishingHandshakes || shutdown) return;
// If the remote computer has sent us its entire BitTorrent handshake, and we've sent it ours
if (incomingDone && !outgoingHandshake.hasRemaining()) {
// Record that we're done with the handshake
finishingHandshakes = true;
// Make a new TorrentLocation object to represent the remote computer we just finished shaking hands with
TorrentLocation p = new TorrentLocation(
sock.getInetAddress(), // The remote computer's Internet IP address and port number
sock.getPort(),
peerId, // The remote computer's peer ID and extension bytes it told us in the handshake
extBytes);
// Make a new BTConnection object to represent our connection to the remote computer right now
BTConnection btc = new BTConnection(
sock, // The NIOSocket object that connects us
torrent.getMetaInfo(), // The BTMetaInfo object we made from the bencoded data inside the .torrent file
p, // The remote computer's IP address adn port number in a TorrentLocation object
torrent, // The ManagedTorrent object that represents the .torrent file
true); // True, we initiated the connection
// List this new connection with the ManagedTorrent object
if (LOG.isDebugEnabled()) LOG.debug("added outgoing connection " + sock.getInetAddress().getHostAddress());
torrent.addConnection(btc);
// Tell the torrent's BTConnectionFetcher we made this connection
torrent.getFetcher().handshakerDone(this);
}
}
/**
* There was an error with our connection to this remote computer.
*
* 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 us
*/
public void handleIOException(IOException iox) {
// Tell this torrent's BTConnectionFetcher that this BTHandshaker object is done, and close our socket to the remote computer
shutdown();
}
/**
* Shut down this BTHandshaker object.
* Tells this torrent's BTConnectionFetcher that this BTHandshaker object is done.
* Closes our socket connection to the remote computer.
*/
public void shutdown() {
// Only perform these steps once
synchronized(this) {
if (shutdown) return;
shutdown = true; // Mark that this BTHandshaker object has been shut down
}
// Tell this torrent's BTConnectionFetcher that this BTHandshaker is finished
if (torrent != null) torrent.getFetcher().handshakerDone(this);
// Close our socket to the remote computer
if (sock != null) try { sock.close(); } catch (IOException impossible) {}
}
/**
* Set the channel this object will write to.
*
* @param newChannel An InterestWriteChannel this object can send data to
*/
public void setWriteChannel(InterestWriteChannel newChannel) {
// Save the given channel in this object
writeChannel = newChannel;
}
/**
* Get the channel this object writes to.
*
* @return The InterestWriteChannel this object sends data to
*/
public InterestWriteChannel getWriteChannel() {
// Return the object we saved and have been using
return writeChannel;
}
/**
* Set the channel this object will read from.
*
* @param newChannel An InterestReadChannel this object can read data from
*/
public void setReadChannel(InterestReadChannel newChannel) {
/*
* if this throws, we've got problems
*/
// Save the given channel in this object
readChannel = (InterestScatteringByteChannel)newChannel;
}
/**
* Get the channel this object reads from.
*
* @return The InterestReadChannel this object gets data from
*/
public InterestReadChannel getReadChannel() {
// Return the object we saved and have been using
return readChannel;
}
}