// Commented for the Learning branch package com.limegroup.bittorrent; import java.io.IOException; import java.net.InetAddress; import java.util.Arrays; import com.bitzi.util.Base32; import com.limegroup.gnutella.ByteOrder; import com.limegroup.gnutella.Endpoint; import com.limegroup.gnutella.ErrorService; /** * A TorrentLocation object represents a remote computer on the Internet running BitTorrent software. * It's downloading files with BitTorrent, so we can use it as a location to get parts of files from. * * The TorrentLocation class extends Endpoint to have an IP address and port number. * Call methods like getAddress() and getPort() to get the IP address and port number of the remote BitTorrent computer. * * It adds the BitTorrent peer ID, and the extension bytes. * A peer ID is 20-bytes like "LIMEguidguidguidguid". * It starts with the vendor code written in 4 ASCII bytes. * After that is the data of the 16-byte GUID. * There are 8 extension bytes, like EEEEEEEE, with bits set for the advanced BitTorrent features that the remote computer understands and supports. * LimeWire supports alternate locations, set in 0x02 in the last byte. * * TorrentLocation also has code that keeps track of how many times we've tried and failed to connect to this address. * After the second failure, isOut() returns true, and we shouldn't try a 3rd time. * isBusy() returns true if we've tried and failed in the last 5 minutes, and should wait longer before trying again. */ public class TorrentLocation extends Endpoint { /** A long unique number that identifies this version of a TorrentLocation object serialized to disk. */ private static final long serialVersionUID = 7953314787152210101L; /** A 20-byte array filled with 0s, use when we don't know this remote computer's BitTorrent peer ID. */ private static final byte [] NULL_PEER_STRING = new byte[20]; /** An 8-byte array filled with 0x, use when we don't know this remote computer's BitTorrent extension bytes. */ private static final byte[] ZERO_BYTES = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; /** 1, after we're unable to contact this address 2 times, we'll give up on it. */ private static final int MAX_STRIKES = 1; /** 300,000 milliseconds, 5 minutes, after we are unable to contact this peer, we'll wait 5 minutes before trying again. */ private static final int BUSY_WAIT_TIME = 5 * 60 * 1000; /** The 20-byte peer ID that identifies the remote computer, like "LIMEguidguidguidguid". */ private final byte[] PEER_ID; /** The remote computer's 8 extension bytes, that tell what advanced BitTorrent extensions it understands. */ private final byte[] EXTENSION_BYTES; /** The number of times we've tried to connect to this address, and failed. */ private int _strikes = 0; /** The time when we'll next let ourselves try to connect to this address again. */ private long _nextRetryTime = 0; /** Not used. */ private String _userAgent = null; /** * Make a new TorrentLocation object to hold the IP address, peer ID, and extension bytes of a remote computer running BitTorrent software. * * @param address The IP address of the remote computer. * @param port The port number of the remote computer. * @param peerId The 20-byte peer ID of the remote computer, like "LIMEguidguidguidguid". * null if we don't know it, this new TorrentLocation will have a peer ID of 20 zero bytes. * @param extensionBytes The remote computer's 8 extension bytes, which tell which advanced BitTorrent features it supports. */ public TorrentLocation(InetAddress address, int port, byte[] peerId, byte[] extensionBytes) { // Call the Endpoint constructor to save the IP address and port number super(address.getHostAddress(), port); // Set this remote computer's peer ID, the 20 bytes that identify it uniquely among BitTorrent programs PEER_ID = (peerId == null) ? // If the caller passed null instead of a peer ID NULL_PEER_STRING : // Use 20 bytes of 0s instead, otherwise peerId; // Use the given peer ID // Save the given extension bytes, 8 bytes that tell which advanced BitTorrent features the computer supports EXTENSION_BYTES = extensionBytes; } /** * Make a new TorrentLocation object to hold the IP address, peer ID, and extension bytes of a remote computer running BitTorrent software. * * @param address The IP address of the remote computer * @param port The port number of the remote computer * @param peerId The 20-byte peer ID of the remote computer, like "LIMEguidguidguidguid". * null if we don't know it, this new TorrentLocation will have a peer ID of 20 zero bytes. */ public TorrentLocation(InetAddress address, int port, byte[] peerId) { // Call the Endpoint constructor to save the IP address and port number super(address.getHostAddress(), port); // Set this remote computer's peer ID, the 20 bytes that identify it uniquely among BitTorrent programs PEER_ID = (peerId == null) ? // If the caller passed null instead of a peer ID NULL_PEER_STRING : // Use 20 bytes of 0s instead, otherwise peerId; // Use the given peer ID // Save the given extension bytes, 8 bytes that tell which advanced BitTorrent features the computer supports EXTENSION_BYTES = ZERO_BYTES; } /** * Make a copy of a given TorrentLocation object. * * @param to The TorrentLocation object to copy */ public TorrentLocation(TorrentLocation to) { // Call another constructor to make this new object, giving it all the information of the given one this(to.getInetAddress(), to.getPort(), to.getPeerID(), to.EXTENSION_BYTES); } /** * Get this remote computer's BitTorrent peer ID. * * The peer ID is 20 bytes like "LIMEguidguidguidguid". * The first 4 bytes are ASCII text that tell the vendor code. * The 16 bytes after that are a GUID that makes sure the peer ID is unique. * * @return A 20-byte array with this computer's BitTorrent peer ID. * If we don't know the peer ID, returns 20 bytes of 0s. */ public byte[] getPeerID() { // Return the peer ID we saved return PEER_ID; } /** * Call strike() if you've tried to connect to this address, and failed. * Counts the failure, and sets the time we'll next try to connect to 5 minutes from now. */ public void strike() { // Set the next retry time to 5 minutes from now _nextRetryTime = System.currentTimeMillis() + BUSY_WAIT_TIME; // Count that we've tried and failed to connect to this remote computer another time _strikes++; } /** * Find out if we can try to connect to this address, or if we should wait longer before trying. * * If we've tried and failed to connect in the last 5 minutes, returns true. * If we've never tried, or never failed, or failed more than 5 minutes ago, returns false. * * @param now The time now, the number of milliseconds since 1970. * @return False if you can try connecting to this address now. * True to wait longer before trying. */ public boolean isBusy(long now) { // Return true if now hasn't reached our next retry time yet return _nextRetryTime > now; } /** * Find out how much longer we have to wait before we'll let ourselves try connecting to this address again. * * @param now The time now, the number of milliseconds since 1970. * @return The number of milliseconds we have to wait before we can try connecting to this address again. * 0 if we can try now. */ public long getWaitTime(long now) { // Compute how much longer we have to wait return Math.max(0, _nextRetryTime - now); // Return 0 instead of negative } /** * Find out if we've failed connecting to this address enough times that we should give up on it entirely. * * A TorentLocation can have one failure, and we'll try again 5 minutes later. * After the second failure, isOut() will return true. * * @return False if this TorrentLocation has 0 or 1 failures, and we can try again. * True if this TorrentLocation has 2 or more failures, and we shouldn't try it anymore. */ public boolean isOut() { // Return true if we've failed 2 times return _strikes > MAX_STRIKES; } /** * Compose a 6-byte array like "IIIIPP", with the IP address in the first 4 bytes and the port in the last 2. * The IP address is in network byte order, and the port number is in big endian. * * @return The byte array */ public byte[] toBytes() { // Make a new 6-byte array to fill and return byte[] ret = new byte[6]; // Copy the IP address into the first 4 bytes try { System.arraycopy(getHostBytes(), 0, ret, 0, 4); } catch (IOException ioe) { ErrorService.error(ioe); } // At 4 bytes, write the 2-byte port number in big endian order ByteOrder.short2beb((short)getPort(), ret, 4); // Return the byte array we made return ret; } /** * Determine if this address is to a remote computer running LimeWire BitTorrent software. * Looks for "LIME" in the first 4 bytes of the 20-byte BitTorrent peer ID. * * @return True if the peer ID starts "LIME", meaning the remote computer is running LimeWire. * False if it's running something else. */ public boolean isLimePeer() { // Return true if the first 4 letters in the peer ID are "LIME" return PEER_ID[0] == (byte)'L' && PEER_ID[1] == (byte)'I' && PEER_ID[2] == (byte)'M' && PEER_ID[3] == (byte)'E'; } /** * Determine if this remote computer supports alternate location requests. * Looks in the extension bytes for the 2 bit set in the last byte. * * TODO: Ask Gregorio where is this documented, if anywhere * * @return True if this remote computer's extension bytes indicate support for alt-loc requests. * False if they don't. */ public boolean supportsAltLocRequests() { // Look at the last extension byte, and see if the 2 bit is set return (0x02 & EXTENSION_BYTES[7]) == 0x02; } /** * Compare this TorrentLocation object to another. * * Compares the IP addresses and peer IDs. * Doesn't look at the port numbers or extension bytes. * * @return True if they are the same, false if they are different */ public boolean equals(Object o) { // Make sure the given object is a TorrentLocation if (!(o instanceof TorrentLocation)) return false; TorrentLocation other = (TorrentLocation)o; // Compare their IP addresses if (!other.getAddress().equals(getAddress())) return false; // Compare their peer IDs if (!Arrays.equals(other.PEER_ID, this.PEER_ID)) return false; // If the IP address and peer IDs are the same, report the objects are the same return true; } /** * Express this TorrentLocation as text. * Composes a String like: * * 1.2.3.4:5:LIMEguidguidguidguid:base32extensionbytes * * The parts are separated by ":" * First are the IP address and port number. * After that is the 20-byte peer ID, converted into a String. * The vendor code at the start will be readable, while the guid may not be. * Last are the extension bytes, written in base 32 encoding. * * @return A String */ public String toString() { // Compose and return the String return getAddress() + ":" + getPort() + ":" + new String(PEER_ID) + ":" + Base32.encode(EXTENSION_BYTES); } }