package com.limegroup.gnutella;
import java.util.Comparator;
import java.util.Random;
import com.limegroup.gnutella.util.NetworkUtils;
/**
* A 16-bit globally unique ID. Immutable.<p>
*
* Let the bytes of a GUID G be labelled G[0]..G[15]. All bytes are unsigned.
* Let a "short" be a 2 byte little-endian** unsigned number. Let AB be the
* short formed by concatenating bytes A and B, with B being the most
* significant byte. LimeWire GUID's have the following properties:
*
* <ol>
* <li>G[15]=0x00. This is reserved for future use.
* <li>G[9][10]= tag(G[4][5], G[6][7]). This is LimeWire's "secret"
* proprietary marking.
* </ol>
*
* Here tag(A, B)=OxFFFF & ((A+2)*(B+3) >> 8). In other words, the result is
* obtained by first taking pair of two byte values and adding "secret"
* constants. These two byte values are then multiplied together to form a 4
* byte product. The middle two bytes of this product are the tag. <b>Sign IS
* considered during this process, since Java does that by default.</b><p>
*
* As of 9/2004, LimeWire GUIDs used to be marked as such:
* <li>G[8]==0xFF. This serves to identify "new GUIDs", e.g. from BearShare.
* This marking was deprecated.
*
* In addition, LimeWire GUIDs may be marked as follows:
* <ol>
* <li>G[13][14]=tag(G[0]G[1], G[9][10]). This was used by LimeWire 2.2.0-2.2.3
* to mark automatic requeries. Unfortunately these versions inadvertently sent
* requeries when cancelling uploads or when sometimes encountering a group of
* busy hosts. VERSION 0
* </ol>
* <li>G[13][14]=tag(G[0][1], G[2][3]). This marks requeries from versions of
* LimeWire that have fixed the requery bug, e.g., 2.2.4 and all 2.3s. VERSION
* 1
* </ol>
* <li>G[13][14]=tag(G[0][1], G[11][12]). This marks requeries from versions of
* LimeWire that have much reduced the amount of requeries that can be sent by
* an individual client. a client can only send 32 requeries amongst ALL
* requeries a day. VERSION 2
* </ol>
*
* Note that this still leaves 10-12 bytes for randomness. That's plenty of
* distinct GUID's. And there's only a 1 in 65000 chance of mistakenly
* identifying a LimeWire.
*
* Furthermore, LimeWire GUIDs may be 'marked' by containing address info. In
* particular:
* <ol>
* <li>G[0][3] = 4-octet IP address. G[13][14] = 2-byte port (little endian).
* </ol>
* Note that there is no way to tell from a guid if it has been marked in this
* manner. You need to have some indication external to the guid (i.e. for
* queries the minSpeed field might have a bit set to indicate this). Also,
* this reduces the amount of guids per IP to 2^48 - plenty since IP and port
* comboes are themselves unique.
*
*/
public class GUID implements Comparable {
/** The size of a GUID. */
private static final int SZ=16;
/** Used to generated new GUID's. */
private static Random rand=new Random();
/** The contents of the GUID. INVARIANT: bytes.length==SZ */
private byte[] bytes;
/**
* Creates a new Globally Unique Identifier (GUID).
*/
public GUID() {
this(makeGuid());
}
/**
* Creates a new <tt>GUID</tt> instance with the specified array
* of unique bytes.
*
* @param bytes the array of unique bytes
*/
public GUID(byte[] bytes) {
Assert.that(bytes.length==SZ);
this.bytes=bytes;
}
/** See the class header description for more details.
* @param first The first index of the first two bytes involved in the
* marking.
* @param second The first index of the second two bytes involved in the
* marking.
* @param markPoint The first index of the two bytes where to put the
* marking.
*/
private static void tagGuid(byte[] guid,
int first,
int second,
int markPoint) {
// You could probably avoid calls to ByteOrder as an optimization.
short a=ByteOrder.leb2short(guid, first);
short b=ByteOrder.leb2short(guid, second);
short tag=tag(a, b);
ByteOrder.short2leb(tag, guid, markPoint);
}
/** Returns the bytes for a new GUID. */
public static byte[] makeGuid() {
//Start with random bytes. You could avoid filling them all in,
//but it's not worth it.
byte[] ret=new byte[16];
rand.nextBytes(ret);
//Apply common tags.
ret[15]=(byte)0x00; //Version number is 0.
//Apply LimeWire's marking.
tagGuid(ret, 4, 6, 9);
return ret;
}
/** @return the bytes for a new GUID flagged to be a requery made by LW.
*/
public static byte[] makeGuidRequery() {
byte[] ret = makeGuid();
//Apply LimeWire's marking.
tagGuid(ret, 0, 11, 13);
return ret;
}
/** Create a guid with an ip and port encoded within.
* @exception IllegalArgumentException thrown if ip.length != 4 or if the
* port is not a valid value.
*/
public static byte[] makeAddressEncodedGuid(byte[] ip, int port)
throws IllegalArgumentException {
return addressEncodeGuid(makeGuid(), ip, port);
}
/** Modifies the input guid by address encoding it with the ip and port.
* @exception IllegalArgumentException thrown if ip.length != 4 or if the
* port is not a valid value or if the size of the input guid is not 16.
* @returns the input guid, now modified as appropriate. note that since
* you have a handle to the original bytes you don't need the return value.
*/
public static byte[] addressEncodeGuid(byte[] ret, byte[] ip, int port)
throws IllegalArgumentException {
if (ret.length != SZ)
throw new IllegalArgumentException("Input byte array wrong length.");
if (!NetworkUtils.isValidAddress(ip))
throw new IllegalArgumentException("IP is invalid!");
if (!NetworkUtils.isValidPort(port))
throw new IllegalArgumentException("Port is invalid: " + port);
// put the IP in there....
for (int i = 0; i < 4; i++)
ret[i] = ip[i];
// put the port in there....
ByteOrder.short2leb((short) port, ret, 13);
return ret;
}
/** Returns LimeWire's secret tag described above. */
static short tag(short a, short b) {
int product=(a+2)*(b+3);
//No need to actually do the AND since the downcast does that.
short productMiddle=(short)(product >> 8);
return productMiddle;
}
/** Same as isLimeGUID(this.bytes) */
public boolean isLimeGUID() {
return isLimeGUID(this.bytes);
}
/** Same as isLimeRequeryGUID(this.bytes, version) */
public boolean isLimeRequeryGUID(int version) {
return isLimeRequeryGUID(this.bytes, version);
}
/** Same is isLimeRequeryGUID(this.bytes) */
public boolean isLimeRequeryGUID() {
return isLimeRequeryGUID(this.bytes);
}
/** Same as addressesMatch(this.bytes, ....) */
public boolean addressesMatch(byte[] ip, int port)
throws IllegalArgumentException {
return addressesMatch(this.bytes, ip, port);
}
/** Same as getIP(this.bytes) */
public String getIP() {
return getIP(this.bytes);
}
/** Same as matchesIP(this.bytes) */
public boolean matchesIP(byte[] bytes) {
return matchesIP(bytes, this.bytes);
}
/** Same as getPort(this.bytes) */
public int getPort() {
return getPort(this.bytes);
}
private static boolean checkMatching(byte[] bytes,
int first,
int second,
int found) {
short a = ByteOrder.leb2short(bytes, first);
short b = ByteOrder.leb2short(bytes, second);
short foundTag = ByteOrder.leb2short(bytes, found);
short expectedTag = tag(a, b);
return foundTag == expectedTag;
}
/** Returns true if this is a specially marked LimeWire GUID.
* This does NOT mean that it's a new GUID as well; the caller
* will probably want to check that. */
public static boolean isLimeGUID(byte[] bytes) {
return checkMatching(bytes, 4, 6, 9);
}
/** Returns true if this is a specially marked Requery GUID from any version
* of LimeWire. This does NOT mean that it's a new GUID as well; the
* caller will probably want to check that.
*/
public static boolean isLimeRequeryGUID(byte[] bytes) {
return isLimeRequeryGUID(bytes, 0) ||
isLimeRequeryGUID(bytes, 1) || isLimeRequeryGUID(bytes, 2);
}
/** Returns true if this is a specially marked LimeWire Requery GUID.
* This does NOT mean that it's a new GUID as well; the caller
* will probably want to check that.
*
* @param version The version of RequeryGUID you want to test for. 0 for
* requeries up to 2.2.4, 1 for requeries between 2.2.4 and all 2.3s, and 2
* for current requeries....
*/
public static boolean isLimeRequeryGUID(byte[] bytes, int version) {
if (version == 0)
return checkMatching(bytes, 0, 9, 13);
else if (version == 1)
return checkMatching(bytes, 0, 2, 13);
else
return checkMatching(bytes, 0, 11, 13);
}
/** @return true if the input ip and port match the one encoded in the guid.
* @exception IllegalArgumentException thrown if ip.length != 4 or if the
* port is not a valid value.
*/
public static boolean addressesMatch(byte[] guidBytes, byte[] ip, int port)
throws IllegalArgumentException {
if (ip.length != 4)
throw new IllegalArgumentException("IP address too big!");
if (!NetworkUtils.isValidPort(port))
return false;
if (!NetworkUtils.isValidAddress(ip))
return false;
byte[] portBytes = new byte[2];
ByteOrder.short2leb((short) port, portBytes, 0);
return ((guidBytes[0] == ip[0]) &&
(guidBytes[1] == ip[1]) &&
(guidBytes[2] == ip[2]) &&
(guidBytes[3] == ip[3]) &&
(guidBytes[13] == portBytes[0]) &&
(guidBytes[14] == portBytes[1]));
}
/** Gets bytes 0-4 as a dotted ip address.
*/
public static String getIP(byte[] guidBytes) {
return NetworkUtils.ip2string(guidBytes);
}
/** Gets bytes 0-4 as a dotted ip address.
*/
public static boolean matchesIP(byte[] ipBytes, byte[] guidBytes) {
if (ipBytes.length != 4)
throw new IllegalArgumentException("Bad byte[] length = " +
ipBytes.length);
for (int i = 0; i < ipBytes.length; i++)
if (ipBytes[i] != guidBytes[i]) return false;
return true;
}
/** Gets bytes 13-14 as a port.
*/
public static int getPort(byte[] guidBytes) {
return ByteOrder.ushort2int(ByteOrder.leb2short(guidBytes, 13));
}
/**
* Compares this GUID to o, lexically.
*/
public int compareTo(Object o) {
if (this == o)
return 0;
else if (o instanceof GUID)
return compare(this.bytes(), ((GUID)o).bytes());
else
return 1;
}
public static final Comparator GUID_COMPARATOR = new GUIDComparator();
public static final Comparator GUID_BYTE_COMPARATOR = new GUIDByteComparator();
/** Compares GUID's lexically. */
public static class GUIDComparator implements Comparator {
public int compare(Object a, Object b) {
return GUID.compare(((GUID)a).bytes, ((GUID)b).bytes);
}
}
/** Compares 16-byte arrays (raw GUIDs) lexically. */
public static class GUIDByteComparator implements Comparator {
public int compare(Object a, Object b) {
return GUID.compare((byte[])a, (byte[])b);
}
}
/** Compares guid and guid2 lexically, which MUST be 16-byte guids. */
private static final int compare(byte[] guid, byte[] guid2) {
for (int i=0; i<SZ; i++) {
int diff=guid[i]-guid2[i];
if (diff!=0)
return diff;
}
return 0;
}
public boolean equals(Object o) {
//The following assertions are to try to track down bug X96.
if (! (o instanceof GUID))
return false;
Assert.that(o!=null, "Null o in GUID.equals");
byte[] bytes2=((GUID)o).bytes();
Assert.that(bytes!=null, "Null bytes in GUID.equals");
Assert.that(bytes2!=null, "Null bytes2 in GUID.equals");
for (int i=0; i<SZ; i++)
if (bytes[i]!=bytes2[i])
return false;
return true;
}
public int hashCode() {
//Glum bytes 0..3, 4..7, etc. together into 32-bit numbers.
byte[] ba=bytes;
final int M1=0x000000FF;
final int M2=0x0000FF00;
final int M3=0x00FF0000;
int a=(M1&ba[0])|(M2&ba[1]<<8)|(M3&ba[2]<<16)|(ba[3]<<24);
int b=(M1&ba[4])|(M2&ba[5]<<8)|(M3&ba[6]<<16)|(ba[7]<<24);
int c=(M1&ba[8])|(M2&ba[9]<<8)|(M3&ba[10]<<16)|(ba[11]<<24);
int d=(M1&ba[12])|(M2&ba[13]<<8)|(M3&ba[14]<<16)|(ba[15]<<24);
//XOR together to yield new 32-bit number.
return a^b^c^d;
}
/** Warning: this exposes the rep! Do not modify returned value. */
public byte[] bytes() {
return bytes;
}
public String toString() {
return toHexString();
}
/**
* Create a hex version of a GUID for compact display and storage
* Note that the client guid should be read in with the
* Integer.parseByte(String s, int radix) call like this in reverse
*/
public String toHexString() {
StringBuffer buf=new StringBuffer();
String str;
int val;
for (int i=0; i<SZ; i++) {
//Treating each byte as an unsigned value ensures
//that we don't str doesn't equal things like 0xFFFF...
val = ByteOrder.ubyte2int(bytes[i]);
str = Integer.toHexString(val);
while ( str.length() < 2 )
str = "0" + str;
buf.append( str );
}
return buf.toString().toUpperCase();
}
/**
* Create a GUID bytes from a hex string version.
* Throws IllegalArgumentException if sguid is
* not of the proper format.
*/
public static byte[] fromHexString(String sguid)
throws IllegalArgumentException {
byte bytes[] = new byte[SZ];
try {
for (int i=0; i<SZ; i++) {
bytes[i] =
(byte)Integer.parseInt(sguid.substring(i*2,(i*2)+2), 16);
}
return bytes;
} catch (NumberFormatException e) {
throw new IllegalArgumentException();
} catch (IndexOutOfBoundsException e) {
throw new IllegalArgumentException();
}
}
/** Simply couples a GUID with a timestamp. Needed for expiration of
* QueryReplies waiting for out-of-band delivery, expiration of proxied
* GUIDs, etc.
*/
public static class TimedGUID {
private final long MAX_LIFE;
private final GUID _guid;
public GUID getGUID() { return _guid; }
private final long _creationTime;
/**
* @param guid The GUID to 'time'.
* @param maxLife The max lifetime of this GUID.
*/
public TimedGUID(GUID guid, long maxLife) {
_guid = guid;
MAX_LIFE = maxLife;
_creationTime = System.currentTimeMillis();
}
/** @return true if other is a GUID that is the same as the GUID
* in this bundle.
*/
public boolean equals(Object other) {
if (other == this) return true;
if (other instanceof TimedGUID)
return _guid.equals(((TimedGUID) other)._guid);
return false;
}
/** Since guids will be all we have when we do a lookup in a hashtable,
* we want the hash code to be the same as the GUID.
*/
public int hashCode() {
return _guid.hashCode();
}
/** @return true if this bundle is greater than MAX_LIFE seconds old.
*/
public boolean shouldExpire() {
long currTime = System.currentTimeMillis();
if (currTime - _creationTime >= MAX_LIFE)
return true;
return false;
}
}
//Unit test: in the tests project.
}