// Commented for the Learning branch package com.limegroup.bittorrent; import java.io.UnsupportedEncodingException; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.limegroup.gnutella.ByteOrder; import com.limegroup.gnutella.Constants; import com.limegroup.gnutella.ErrorService; import com.limegroup.bittorrent.settings.BittorrentSettings; import com.limegroup.gnutella.util.NetworkUtils; import com.sun.org.apache.bcel.internal.generic.GETSTATIC; /** * A TrackerResponse represents the bencoded response a BitTorrent tracker sent us in response to our message to it. * * The response is a bencoded dictionary. * The TrackerResponse constructor reads the following keys: * * failure reason A string that describes why the tracker is unable to help us now. * peers The value is a string like "IIIIPPIIIIPPIIIIPP" that has the IP addresses and port numbers of peers. * Or, the value is a list of dictionaries like this: * ip The IP address as a string, like "1.2.3.4". * port The port number. * peer id The 20-byte peer ID. * interval We should contact the tracker every time this number of seconds expire. * min_interval If "interval" is missing, we'll use "min_interval" instead. * num peers The number of leachers the tracker knows about for this torrent, addresses of peers that don't have the whole file yet. * incomplete If "num peers" is missing, read "incomplete" instead. * num done The number of seeders the tracker knows about for this torrent, addresses of peers that have the whole file. * complete If "num done" is missing, read "complete" instead. * * A TrackerResponse object has the following public member variables you can read: * * FAILURE_REASON The reason the tracker can't help us. * PEERS An ArrayList of TorrentLocation objects with the IP addresses and port numbers the tracker gave us. * INTERVAL We should contact the tracker ever INTERVAL seconds. * NUM_PEERS The number of leachers the tracker knows about for this torrent, peers that don't have the whole file yet. * DONE_PEERS The number of seeders the tracker knows about for this torrent, peers with the whole file. */ public class TrackerResponse { /** A debugging log we can write lines of text to as the program runs. */ private static final Log LOG = LogFactory.getLog(TrackerResponse.class); /** * The list of peers the tracker gave us. * These are the IP addresses and port numbers of remote computers on the Internet running BitTorrent programs that are sharing the torrent we asked the tracker about. * * The tracker's response is a bencoded dictionary that has the key "peers". * The value is probably a string like "IIIIPPIIIIPPIIIIPP", with 6-byte chunks that have an IP address and port number. * * Or, the value can be a bencoded list. * Each element in the list is a dictionary like this: * * ip The IP address, like "1.2.3.4" * port The port number * peer id The 20-byte peer ID, like "LIMEguidguidguidguid" * * A TorrentLocation object keeps an IP address, port number, and 20-byte peer ID together. * If "peers" was a string, we don't know the peer IDs. * Each TorrentLocation's peer ID member will point to a byte array of 20 zero bytes. */ public final List PEERS; /** * We should contact the tracker every INTERVAL seconds. * Read from the "interval" or, if that's not found "min_interval" keys in the tracker's response. * Between 5 minutes and 2 hours. */ public final int INTERVAL; /** * The tracker has this many leachers for the torrent. * The number of addresses the tracker has of peers that don't have the whole torrent yet. * Read from the "num peers", or, if that's not found, "incomplete" keys in the tracker's response. */ public final int NUM_PEERS; /** * The tracker has this many seeders for the torrent. * The number of addresses the tracker has of peers that have the whole torrent. * Read from the "num done", or, if that's not found, "complete" keys in the tracker's response. */ public final int DONE_PEERS; /** * The reason the tracker can't help us. * The String value of the key "failure reason". */ public final String FAILURE_REASON; /** * Make a new TrackerResponse object to represent the bencoded response a BitTorrent tracker gave us. * Only connectHTTP() calls this constructor. * * Here are the parts of the bencoded data it looks for: * * failure reason A string that describes why the tracker is unable to help us now. * peers The value is a string like "IIIIPPIIIIPPIIIIPP" that has the IP addresses and port numbers of peers. * Or, the value is a list of dictionaries like this: * ip The IP address as a string, like "1.2.3.4". * port The port number. * peer id The 20-byte peer ID. * interval We should contact the tracker every time this number of seconds expire. * min_interval If "interval" is missing, we'll use "min_interval" instead. * num peers The number of leachers the tracker knows about for this torrent, addresses of peers that don't have the whole file yet. * incomplete If "num peers" is missing, read "incomplete" instead. * num done The number of seeders the tracker knows about for this torrent, addresses of peers that have the whole file. * complete If "num done" is missing, read "complete" instead. * * @param t_response A Java HashMap made from parsing the bencoded data a BitTorrent tracker sent us */ public TrackerResponse(Object t_response) throws ValueException { // Make sure the caller gave us a Map if (!(t_response instanceof Map)) throw new ValueException("bad tracker response"); Map response = (Map)t_response; // If the tracker sent the key "failure reason", get the string value if (response.containsKey("failure reason")) FAILURE_REASON = new String((byte[])response.get("failure reason")); else FAILURE_REASON = null; // The IP addresses and port numbers of computers with BitTorrent programs sharing the same torrent are under "peers" if (response.containsKey("peers")) { // The value of "peers" can be a bencoded List, or a byte array of 6-byte IP addresses and port numbers Object t_peers = response.get("peers"); if (t_peers instanceof List) PEERS = parsePeers((List)t_peers); // Parse the bencoded list else if (t_peers instanceof byte[]) PEERS = parsePeers((byte[])t_peers); // Parse the 6-byte chunks else throw new ValueException("bad tracker response - bad peers " + t_peers); // "peers" not found } else { // Point PEERS at an empty list PEERS = Collections.EMPTY_LIST; } // Read "interval" and "min_interval", the times in seconds we should put between requests to the tracker Object t_interval = response.get("interval"); // The "interval" key is required Object t_minInterval = response.get("min_interval"); // The tracker may have also sent "min_interval", which is the same thing int interval; // Make a variable for the value we'll use if (t_interval instanceof Long) { // The response has "interval" interval = (int)((Long)t_interval).longValue(); // Use it } else if (t_minInterval instanceof Long) { // The response doesn't have "interval", but does have "min_interval" instead interval = (int) ((Long)t_interval).longValue(); // Use it instead } else { // The response doesn't have either interval = BittorrentSettings.TRACKER_MIN_REASK_INTERVAL.getValue(); // Use our default of 5 minutes } // Move the interval into our required range of 5 minutes to 2 hours if (interval < BittorrentSettings.TRACKER_MIN_REASK_INTERVAL.getValue()) INTERVAL = BittorrentSettings.TRACKER_MIN_REASK_INTERVAL.getValue(); // 5 minutes else if (interval > BittorrentSettings.TRACKER_MAX_REASK_INTERVAL.getValue()) INTERVAL = BittorrentSettings.TRACKER_MAX_REASK_INTERVAL.getValue(); // 2 hours else INTERVAL = interval; // Read "num peers" or "incomplete" to get the number of peers that don't have the entire file yet, the leachers Object t_numPeers = response.get("num peers"); if (t_numPeers instanceof Long) { NUM_PEERS = (int)((Long)t_numPeers).longValue(); } else { // "num peers" not found t_numPeers = response.get("incomplete"); // Read "incomplete" instead if (t_numPeers instanceof Long) { NUM_PEERS = (int)((Long)t_numPeers).longValue(); } else { NUM_PEERS = 0; // "incomplete" not found, set to 0 } } // Read "num done" or "complete", the number of peers with the entire file, the number of seeders Object t_donePeers = response.get("num done"); if (t_donePeers instanceof Long) { DONE_PEERS = (int)((Long)t_donePeers).longValue(); } else { // "num done" not found t_donePeers = response.get("complete"); // Read "complete" instead if (t_donePeers instanceof Long) { DONE_PEERS = (int)((Long)t_donePeers).longValue(); } else { DONE_PEERS = 0; // "complete" not found, set to 0 } } } /** * Make a new TrackerResponse object with information a UDP tracker sent us in a UDP packet. * UDPTrackerRequest methods use this constructor. * * @param peers An ArrayList of TorrentLocation objects, the list of peers the tracker gave us * @param interval The interval in seconds the tracker told us to contact it on * @param numPeers The number of leachers, peers without the whole file yet, the tracker knows about for this torrent * @param donePeers The number of seeders, peers with the whole file, the tracker knows about for this torrent * @param failureReason The reason the tracker can't help us */ TrackerResponse(List peers, int interval, int numPeers, int donePeers, String failureReason) { // Save the given information, adjusting interval to be between 5 minutes and 2 hours PEERS = peers; INTERVAL = Math.min(Math.max(interval, BittorrentSettings.TRACKER_MIN_REASK_INTERVAL.getValue()), BittorrentSettings.TRACKER_MAX_REASK_INTERVAL.getValue()); NUM_PEERS = Math.max(0, numPeers); DONE_PEERS = Math.max(0, donePeers); FAILURE_REASON = failureReason; } /** * Parse a bencoded "peers" list into an ArrayList of TorrentLocation objects. * * In the tracker's response, "peers" can either be a byte array or a bencoded list. * This method parses the bencoded list. * Each element in the list is a bencoded dictionary, like this: * * ip The IP address, like "1.2.3.4" * port The port number * peer id The 20-byte peer ID, like "LIMEguidguidguidguid" * * @param peers An ArrayList of HashMap objects made from the bencoded "peers" list in the tracker's response * @return An ArrayList of TorrentLocation objects, each of which has a IP address, port number, and peer ID */ private static List parsePeers(List peers) throws ValueException { // Make a new empty ArrayList we'll fill with TorrentLocation objects and return List ret = new ArrayList(); // Loop for each item in the "peers" list the tracker sent us for (Iterator iter = peers.iterator(); iter.hasNext(); ) { Object t_peer = iter.next(); if (!(t_peer instanceof Map)) throw new ValueException("bad tracker response - bad peer " + t_peer); // Make a TorrentLocation from this address, and add it to the ret list ret.add(parsePeer((Map)t_peer)); } // Return a read-only view of the ArrayList we filled with TorrentLocation objects return Collections.unmodifiableList(ret); } /** * Turn a byte array of IP addresses and port numbers into an ArrayList of TorrentLocation objects. * A TorrentLocation has room for the 20-byte peer ID. * The peer IDs aren't in the byte array, so the TorentLocation objects will point to a byte array of 20 0s. * * @param bytes A byte array like "IIIIPPIIIIPPIIIIPP" with 6-byte chunks with IP addresses in 4 bytes and port numbers in 2 bytes * @return An ArrayList of TorrentLocation objects, each of which holds one IP address and port number */ static List parsePeers(byte[] bytes) throws ValueException { // Make a new empty ArrayList we'll fill with TorrentLocation objects and return ArrayList ret = new ArrayList(); // Move down the given byte array, pointing i at each 6-byte chunk for (int i = 0; i < bytes.length - 5; i += 6) { // Let i reach bytes.length - 6 to reach the last chunk, but not bytes.length // Grab the IP address and port number byte[] address = new byte[4]; System.arraycopy(bytes, i, address, 0, 4); int port = ByteOrder.beb2int(bytes, i + 4, 2); try { // Turn the 4 bytes into an InetAddress object InetAddress addr = InetAddress.getByAddress(address); // Wrap the IP address and port number into a TorrentLocation object, and add it to the list we'll return ret.add(parsePeer(addr, port, null)); // null because we don't know this computer's peer ID // getByAddress() couldn't turn those 4 bytes into an InetAddress object } catch (UnknownHostException uhe) { throw new ValueException("bad tracker response - bad peer ip " + new String(address)); } } // Return a read-only view of the ArrayList we filled with TorrentLocation objects return Collections.unmodifiableList(ret); } /** * Turn a bencoded map from the "peers" list into a TorrentLocation object that represents that BitTorrent peer's address. * Looks for the following elements: * * ip The IP address, like "1.2.3.4" * port The port number * peer id The 20-byte peer ID, like "LIMEguidguidguidguid" * * @param peer A HashMap we made from an element in the bencoded "peers" list from the tracker's response */ private static TorrentLocation parsePeer(Map peer) throws ValueException { // Parse "ip", which has a value like "1.2.3.4", into an InetAddress structure Object t_ip = peer.get("ip"); if (!(t_ip instanceof byte[])) throw new ValueException("bad tracker response - bad peer ip " + t_ip); InetAddress addr; try { String ipS = new String((byte[])t_ip, Constants.ASCII_ENCODING); addr = InetAddress.getByName(ipS); } catch (UnknownHostException uhe) { throw new ValueException("bad tracker response - bad peer ip " + t_ip); } catch (UnsupportedEncodingException impossible) { ErrorService.error(impossible); return null; } // Parse "port", the port number Object t_port = peer.get("port"); if (!(t_port instanceof Long)) throw new ValueException("bad tracker response - bad peer port " + t_port); int port = (int)((Long)t_port).longValue(); // Parse "peer id", the BitTorrent program's 20-byte unique ID Object t_peerId = peer.get("peer id"); if (!(t_peerId instanceof byte[])) throw new ValueException("bad tracker response - bad peer id "); byte[] peerId = (byte[])t_peerId; if (peerId.length != 20) throw new ValueException("bad tracker response - bad peer id "); // Make a new TorrentLocation object from the IP address, port number, and peer ID, and return it return parsePeer(addr, port, peerId); } /** * Wrap a given IP address, port number, and BitTorrent peer ID into a new TorrentLocation object. * * @param addr The IP address of the remote BitTorrent computer as a Java InetAddress object * @param port The remote computer's port number * @param peerId The 20-byte BitTorrent peer ID which uniquely identifies the BitTorrent program * @return A new TorrentLocation object with all that information inside it */ private static TorrentLocation parsePeer(InetAddress addr, int port, byte[] peerId) throws ValueException { // Make sure the IP address and port number look correct if (!NetworkUtils.isValidAddress(addr)) throw new ValueException("bad tracker response - bad peer ip " + addr); if (!NetworkUtils.isValidPort(port)) throw new ValueException("bad tracker response - bad peer port " + port); // Wrap the given information into a new TorrentLocation object, and return it TorrentLocation to = new TorrentLocation(addr, port, peerId); if (LOG.isDebugEnabled()) LOG.debug("got peer " + to); return to; } }