package com.limegroup.gnutella; import java.net.InetAddress; import java.net.UnknownHostException; import com.limegroup.gnutella.util.IpPort; import com.limegroup.gnutella.util.NetworkUtils; import com.limegroup.gnutella.util.StringUtils; /** * Immutable IP/port pair. Also contains an optional number and size * of files, mainly for legacy reasons. */ public class Endpoint implements Cloneable, IpPort, java.io.Serializable { static final long serialVersionUID = 4686711693494625070L; private String hostname = null; int port = 0; /** Number of files at the host, or -1 if unknown */ private long files=-1; /** Size of all files on the host, or -1 if unknown */ private long kbytes=-1; // so subclasses can serialize. protected Endpoint() { } /** * Returns a new Endpoint from a Gnutella-style host/port pair: * <ul> * <li>If hostAndPort is of the format "host:port", where port * is a number, returns new Endpoint(host, port). * <li>If hostAndPort contains no ":" or a ":" at the end of the string, * returns new Endpoint(hostAndPort, 6346). * <li>Otherwise throws IllegalArgumentException. * </ul> */ public Endpoint(String hostAndPort) throws IllegalArgumentException { this(hostAndPort, false); } /** * Same as new Endpoint(hostAndPort) but with additional restrictions on * hostAndPart; if requireNumeric==true and the host part of hostAndPort is * not as a numeric dotted-quad IP address, throws IllegalArgumentException. * Examples: * <pre> * new Endpoint("www.limewire.org:6346", false) ==> ok * new Endpoint("not a url:6346", false) ==> ok * new Endpoint("www.limewire.org:6346", true) ==> IllegalArgumentException * new Endpoint("64.61.25.172:6346", true) ==> ok * new Endpoint("64.61.25.172", true) ==> ok * new Endpoint("127.0.0.1:ABC", false) ==> IllegalArgumentException * </pre> * * If requireNumeric is true no DNS lookups are ever involved. * If requireNumeric is false a DNS lookup MAY be performed if the hostname * is not numeric. * * @see Endpoint (String)) */ public Endpoint(String hostAndPort, boolean requireNumeric) { this(hostAndPort, requireNumeric, true); } /** * Constructs a new endpoint. * If requireNumeric is true, or strict is false, no DNS lookups are ever involved. * If requireNumeric is false or strict is true, a DNS lookup MAY be performed * if the hostname is not numeric. * * To never block, make sure strict is false. */ public Endpoint(String hostAndPort, boolean requireNumeric, boolean strict) { final int DEFAULT=6346; int j=hostAndPort.indexOf(":"); if (j<0) { this.hostname = hostAndPort; this.port=DEFAULT; } else if (j==0) { throw new IllegalArgumentException(); } else if (j==(hostAndPort.length()-1)) { this.hostname = hostAndPort.substring(0,j); this.port=DEFAULT; } else { this.hostname = hostAndPort.substring(0,j); try { this.port=Integer.parseInt(hostAndPort.substring(j+1)); } catch (NumberFormatException e) { throw new IllegalArgumentException(); } if(!NetworkUtils.isValidPort(getPort())) throw new IllegalArgumentException("invalid port"); } if (requireNumeric) { //TODO3: implement with fewer allocations String[] numbers=StringUtils.split(hostname, '.'); if (numbers.length!=4) throw new IllegalArgumentException(); for (int i=0; i<numbers.length; i++) { try { int x=Integer.parseInt(numbers[i]); if (x<0 || x>255) throw new IllegalArgumentException(); } catch (NumberFormatException fail) { throw new IllegalArgumentException(); } } } if(strict && !NetworkUtils.isValidAddress(hostname)) throw new IllegalArgumentException("invalid address: " + hostname); } public Endpoint(String hostname, int port) { this(hostname, port, true); } /** * Constructs a new endpoint using the specific hostname & port. * If strict is true, this does a DNS lookup against the name, * failing if the lookup couldn't complete. */ public Endpoint(String hostname, int port, boolean strict) { if(!NetworkUtils.isValidPort(port)) throw new IllegalArgumentException("invalid port: "+port); if(strict && !NetworkUtils.isValidAddress(hostname)) throw new IllegalArgumentException("invalid address: " + hostname); this.hostname = hostname; this.port=port; } /** * Creates a new Endpoint instance * @param hostBytes IP address of the host (MSB first) * @param port The port number for the host */ public Endpoint(byte[] hostBytes, int port) { if(!NetworkUtils.isValidPort(port)) throw new IllegalArgumentException("invalid port: "+port); if(!NetworkUtils.isValidAddress(hostBytes)) throw new IllegalArgumentException("invalid address"); this.port = port; this.hostname = NetworkUtils.ip2string(hostBytes); } /** * @param files the number of files the host has * @param kbytes the size of all of the files, in kilobytes */ public Endpoint(String hostname, int port, long files, long kbytes) { this(hostname, port); this.files=files; this.kbytes=kbytes; } /** * Creates a new Endpoint instance * @param hostBytes IP address of the host (MSB first) * @param port The port number for the host * @param files the number of files the host has * @param kbytes the size of all of the files, in kilobytes */ public Endpoint(byte[] hostBytes, int port, long files, long kbytes) { this(hostBytes, port); this.files=files; this.kbytes=kbytes; } /** * Constructs a new endpoint from pre-existing endpoint by copying the * fields * @param ep The endpoint from whom to initialize the member fields of * this new endpoint */ public Endpoint(Endpoint ep) { this.files = ep.files; this.hostname = ep.hostname; this.kbytes = ep.kbytes; this.port = ep.port; } public String toString() { return hostname+":"+port; } public String getAddress() { return hostname; } /** * Accessor for the <tt>InetAddress</tt> instance for this host. Implements * <tt>IpPort</tt> interface. * * @return the <tt>InetAddress</tt> for this host, or <tt>null</tt> if the * <tt>InetAddress</tt> cannot be created */ public InetAddress getInetAddress() { try { return InetAddress.getByName(hostname); } catch (UnknownHostException e) { return null; } } public void setHostname(String hostname) { this.hostname = hostname; } public int getPort() { return port; } /** Returns the number of files the host has, or -1 if I don't know */ public long getFiles() { return files; } /** Sets the number of files the host has */ public void setFiles(long files) { this.files = files; } /** Returns the size of all files the host has, in kilobytes, * or -1 if I don't know, it also makes sure that the kbytes/files * ratio is not ridiculous, in which case it normalizes the values */ public long getKbytes() { return kbytes; } /** * If the number of files or the kbytes exceed certain limit, it * considers them as false data, and initializes the number of * files as well as kbytes to zero in that case */ public void normalizeFilesAndSize() { //normalize files try { if(kbytes > 20000000) // > 20GB { files = kbytes = 0; return; } else if(files > 5000) //> 5000 files { files = kbytes = 0; return; } else if (kbytes/files > 250000) //> 250MB/file { files = kbytes = 0; return; } } catch(ArithmeticException ae) { files = kbytes = 0; return; } } /** Sets the size of all files the host has, in kilobytes, */ public void setKbytes(long kbytes) { this.kbytes = kbytes; } /** * Endpoints are equal if their hostnames and ports are. The number * and size of files does not matter. */ public boolean equals(Object o) { if(!(o instanceof Endpoint)) return false; if(o == this) return true; Endpoint e=(Endpoint)o; return hostname.equals(e.hostname) && port==e.port; } public int hashCode() { //This is good enough, since one host rarely has multiple ports. return hostname.hashCode(); } protected Object clone() { return new Endpoint(new String(hostname), port, files, kbytes); } /** *This method returns the IP of the end point as an array of bytes */ public byte[] getHostBytes() throws UnknownHostException { return InetAddress.getByName(hostname).getAddress(); } /** * @requires this and other have dotted-quad addresses, or * names that can be resolved. * @effects Returns true if this is on the same subnet as 'other', * i.e., if this and other are in the same IP class and have the * same network number. */ public boolean isSameSubnet(Endpoint other) { byte[] a; byte[] b; int first; try { a=getHostBytes(); first=ByteOrder.ubyte2int(a[0]); b=other.getHostBytes(); } catch (UnknownHostException e) { return false; } //See http://www.3com.com/nsc/501302.html //class A if (first<=127) return a[0]==b[0]; //class B else if (first <= 191) return a[0]==b[0] && a[1]==b[1]; //class C else return a[0]==b[0] && a[1]==b[1] && a[2]==b[2]; } /** * Determines if this is a UDP host cache. */ public boolean isUDPHostCache() { return false; } }