/*
* Mojito Distributed Hash Table (Mojito DHT)
* Copyright (C) 2006-2007 LimeWire LLC
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.limewire.mojito;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Properties;
import java.util.Random;
import java.util.Map.Entry;
import org.limewire.collection.PatriciaTrie.KeyAnalyzer;
import org.limewire.mojito.util.ArrayUtils;
import org.limewire.security.SecurityUtils;
/**
* KUID stands for Kademlia Unique Identifier and represents
* a 160-bit integer.
* <p>
* This class is immutable!
*/
public class KUID implements Comparable<KUID>, Serializable {
private static final long serialVersionUID = 633717248208386374L;
private static final Random GENERATOR = SecurityUtils.createSecureRandomNoBlock();
public static final int LENGTH = 20;
public static final int LENGTH_IN_BITS = LENGTH * 8; // 160-bit
/** Bits from Most Significant Bits (MSB) to Least Significant Bits (LSB) */
private static final int[] BITS = {
0x80,
0x40,
0x20,
0x10,
0x8,
0x4,
0x2,
0x1
};
/** All 160 bits are 0. */
public static final KUID MINIMUM;
/** All 160 bits are 1. */
public static final KUID MAXIMUM;
static {
byte[] min = new byte[LENGTH];
byte[] max = new byte[LENGTH];
Arrays.fill(max, (byte)0xFF);
MINIMUM = new KUID(min);
MAXIMUM = new KUID(max);
}
/** The id. */
private final byte[] id;
/** The hashCode of this Object. */
private final int hashCode;
protected KUID(byte[] id) {
if (id == null) {
throw new NullPointerException("ID is null");
}
if (id.length != LENGTH) {
throw new IllegalArgumentException("ID must be " + LENGTH + " bytes long");
}
this.id = id;
this.hashCode = Arrays.hashCode(id);
}
/**
* Writes the ID to the OutputStream.
*/
public void write(OutputStream out) throws IOException {
out.write(id, 0, id.length);
}
/**
* Returns whether or not the 'bitIndex' th bit is set.
*/
public boolean isBitSet(int bitIndex) {
// Take advantage of rounding errors!
int index = (bitIndex / BITS.length);
int bit = (bitIndex - index * BITS.length);
return (id[index] & BITS[bit]) != 0;
}
/**
* Sets the specified bit to 1 and returns a new
* KUID instance.
*/
public KUID set(int bit) {
return set(bit, true);
}
/**
* Sets the specified bit to 0 and returns a new
* KUID instance.
*/
public KUID unset(int bit) {
return set(bit, false);
}
/**
* Flips the specified bit from 0 to 1 or vice versa
* and returns a new KUID instance.
*/
public KUID flip(int bit) {
return set(bit, !isBitSet(bit));
}
/**
* Sets or unsets the 'bitIndex' th bit.
*/
private KUID set(int bitIndex, boolean set) {
// Take advantage of rounding errors!
int index = (bitIndex / BITS.length);
int bit = (bitIndex - index * BITS.length);
boolean isBitSet = (id[index] & BITS[bit]) != 0;
// Don't create a new Object if nothing is
// gonna change
if (isBitSet != set) {
byte[] id = getBytes();
id[index] ^= BITS[bit];
return new KUID(id);
} else {
return this;
}
}
/**
* Returns the number of bits that are 1.
*/
public int bits() {
int bits = 0;
for(int i = 0; i < LENGTH_IN_BITS; i++) {
if (isBitSet(i)) {
bits++;
}
}
return bits;
}
/**
* Returns the first bit that differs in this KUID
* and the given KUID or KeyAnalyzer.NULL_BIT_KEY
* if all 160 bits are zero or KeyAnalyzer.EQUAL_BIT_KEY
* if both KUIDs are equal.
*/
public int bitIndex(KUID nodeId) {
boolean allNull = true;
int bitIndex = 0;
for(int i = 0; i < id.length; i++) {
if (allNull && id[i] != 0) {
allNull = false;
}
if (id[i] != nodeId.id[i]) {
for(int j = 0; j < BITS.length; j++) {
if ((id[i] & BITS[j])
!= (nodeId.id[i] & BITS[j])) {
break;
}
bitIndex++;
}
break;
}
bitIndex += BITS.length;
}
if (allNull) {
return KeyAnalyzer.NULL_BIT_KEY;
}
if (bitIndex == LENGTH_IN_BITS) {
return KeyAnalyzer.EQUAL_BIT_KEY;
}
return bitIndex;
}
/**
* Returns the XOR distance between the current and given KUID.
*/
public KUID xor(KUID nodeId) {
byte[] result = new byte[id.length];
for(int i = 0; i < result.length; i++) {
result[i] = (byte)(id[i] ^ nodeId.id[i]);
}
return new KUID(result);
}
/**
* Inverts all bits of the current KUID.
*/
public KUID invert() {
byte[] result = new byte[id.length];
for(int i = 0; i < result.length; i++) {
result[i] = (byte)~id[i];
}
return new KUID(result);
}
/**
*
*
* Returns true if the distance from this KUID to <tt>targetId</tt>, is
* smaller than the distance from <tt>otherId</tt> to <tt>targetId</tt>.
*
* <pre>
* KUID thisKUID = KUID.createWithHexString("0000000000000000000000000000000000000000");
* KUID targetID = KUID.createWithHexString("0000000000000000000000000000000000000001");
* KUID otherID = KUID.createWithHexString("1000000000000000000000000000000000000000");
*
* System.out.println("Distance thisKUID to targetID: " + thisKUID.xor(targetID));
* System.out.println("Distance otherID to targetID: " + otherID.xor(targetID));
*
* System.out.println("thisKUID to targetID is closer than otherID to targetID: "
* + thisKUID.isNearerTo(targetID, otherID));
*
* Output:
* Distance thisKUID to targetID: 0000000000000000000000000000000000000001
* Distance otherID to targetID: 1000000000000000000000000000000000000001
* thisKUID to targetID is closer than otherID to targetID: true
* </pre>
* @param targetId the target ID
* @param otherId the other KUID to compare to
*
* @return true if this KUID is nearer to targetID, false otherwise
*/
public boolean isNearerTo(KUID targetId, KUID otherId) {
int xorToSelf = 0;
int xorToOther = 0;
for (int i = 0; i < id.length; i++){
xorToSelf = (id[i] ^ targetId.id[i]) & 0xFF;
xorToOther = (otherId.id[i] ^ targetId.id[i]) & 0xFF;
if (xorToSelf < xorToOther) {
return true;
} else if (xorToSelf > xorToOther) {
return false;
} // else continue
}
return false;
}
/**
* Returns the raw bytes of the current KUID. The
* returned byte[] array is a copy and modifications
* are not reflected to this KUID.
*/
public byte[] getBytes() {
return getBytes(0, new byte[id.length], 0, id.length);
}
/**
* Returns the raw bytes of the current KUID from the specified interval.
*/
public byte[] getBytes(int srcPos, byte[] dest, int destPos, int length) {
System.arraycopy(id, srcPos, dest, destPos, length);
return dest;
}
@Override
public int hashCode() {
return hashCode;
}
public int compareTo(KUID o) {
int d = 0;
for(int i = 0; i < id.length; i++) {
d = (id[i] & 0xFF) - (o.id[i] & 0xFF);
if (d < 0) {
return -1;
} else if (d > 0) {
return 1;
}
}
return 0;
}
/**
* Returns whether or not both KUIDs are equal.
*/
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
} if (!(o instanceof KUID)) {
return false;
} else {
return Arrays.equals(id, ((KUID)o).id);
}
}
/**
* Returns the current KUID as hex String.
*/
public String toHexString() {
return ArrayUtils.toHexString(id);
}
/**
* Returns the current KUID as bin String.
*/
public String toBinString() {
return ArrayUtils.toBinString(id);
}
/**
* Returns the current KUID as BigInteger.
*/
public BigInteger toBigInteger() {
return new BigInteger(1 /* unsigned! */, id);
}
/**
* Returns the approximate log2. See BigInteger.bitLength()
* for more info!
*/
public int log2() {
return toBigInteger().bitLength();
}
@Override
public String toString() {
return toHexString();
}
/**
* Compute common prefix length of two KUIDs.
*/
public int getCommonPrefixLength(KUID other) {
int commonPrefixLength = 0;
for (int i = 0; i < id.length; i++) {
byte xorValue = (byte)(id[i] ^ other.id[i]);
if (xorValue == 0) {
commonPrefixLength += BITS.length;
} else {
for (int j = 0; j < BITS.length; j++) {
if ((xorValue & BITS[j]) == 0) {
commonPrefixLength++;
} else {
return commonPrefixLength;
}
}
}
}
return commonPrefixLength;
}
/**
* Creates and returns a random ID that is hopefully
* globally unique.
*/
public static KUID createRandomID() {
/*
* Random Numbers.
*/
MessageDigestInput randomNumbers = new MessageDigestInput() {
@Override
public void update(MessageDigest md) {
byte[] random = new byte[LENGTH * 2];
GENERATOR.nextBytes(random);
md.update(random);
}
};
/*
* System Properties. Many of them are not unique but
* properties like user.name, user.home or os.arch will
* add some randomness.
*/
MessageDigestInput properties = new MessageDigestInput() {
@Override
public void update(MessageDigest md) {
Properties props = System.getProperties();
try {
for (Entry entry : props.entrySet()) {
String key = (String)entry.getKey();
String value = (String)entry.getValue();
md.update(key.getBytes("UTF-8"));
md.update(value.getBytes("UTF-8"));
}
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
};
/*
* System time in milliseconds (GMT). Many computer clocks
* are off. Should be a good source for randomness.
*/
MessageDigestInput millis = new MessageDigestInput() {
@Override
public void update(MessageDigest md) {
long millis = System.currentTimeMillis();
md.update((byte)((millis >> 56L) & 0xFFL));
md.update((byte)((millis >> 48L) & 0xFFL));
md.update((byte)((millis >> 40L) & 0xFFL));
md.update((byte)((millis >> 32L) & 0xFFL));
md.update((byte)((millis >> 24L) & 0xFFL));
md.update((byte)((millis >> 16L) & 0xFFL));
md.update((byte)((millis >> 8L) & 0xFFL));
md.update((byte)((millis ) & 0xFFL));
}
};
/*
* VM/machine dependent pseudo time.
*/
MessageDigestInput nanos = new MessageDigestInput() {
@Override
public void update(MessageDigest md) {
long nanos = System.nanoTime();
md.update((byte)((nanos >> 56L) & 0xFFL));
md.update((byte)((nanos >> 48L) & 0xFFL));
md.update((byte)((nanos >> 40L) & 0xFFL));
md.update((byte)((nanos >> 32L) & 0xFFL));
md.update((byte)((nanos >> 24L) & 0xFFL));
md.update((byte)((nanos >> 16L) & 0xFFL));
md.update((byte)((nanos >> 8L) & 0xFFL));
md.update((byte)((nanos ) & 0xFFL));
}
};
/*
* Sort the MessageDigestInput(s) by their random
* index (i.e. shuffle the array).
*/
MessageDigestInput[] input = {
properties,
randomNumbers,
millis,
nanos
};
Arrays.sort(input);
try {
MessageDigest md = MessageDigest.getInstance("SHA1");
// Get the SHA1...
for(MessageDigestInput mdi : input) {
mdi.update(md);
// Hash also the identity hash code
int hashCode = System.identityHashCode(mdi);
md.update((byte)((hashCode >> 24) & 0xFF));
md.update((byte)((hashCode >> 16) & 0xFF));
md.update((byte)((hashCode >> 8) & 0xFF));
md.update((byte)((hashCode ) & 0xFF));
// and the random index
md.update((byte)((mdi.rnd >> 24) & 0xFF));
md.update((byte)((mdi.rnd >> 16) & 0xFF));
md.update((byte)((mdi.rnd >> 8) & 0xFF));
md.update((byte)((mdi.rnd ) & 0xFF));
}
return new KUID(md.digest());
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
private abstract static class MessageDigestInput
implements Comparable<MessageDigestInput> {
private int rnd = GENERATOR.nextInt();
public abstract void update(MessageDigest md);
public int compareTo(MessageDigestInput o) {
return rnd - o.rnd;
}
}
/**
* Creates and returns a KUID from a byte array.
*/
public static KUID createWithBytes(byte[] id) {
byte[] dst = new byte[id.length];
System.arraycopy(id, 0, dst, 0, id.length);
return new KUID(dst);
}
/**
* Creates and returns a KUID from a hex encoded String.
*/
public static KUID createWithHexString(String id) {
return new KUID(ArrayUtils.parseHexString(id));
}
/**
* Creates a KUID from the given InputStream.
*/
public static KUID createWithInputStream(InputStream in) throws IOException {
byte[] id = new byte[LENGTH];
int len = -1;
int r = 0;
while(r < id.length) {
len = in.read(id, r, id.length-r);
if (len < 0) {
throw new EOFException();
}
r += len;
}
return new KUID(id);
}
/**
* Creates a random ID with the specified byte prefix.
*
* @param prefix the fixed prefix bytes
* @param depth of the Bucket in the Trie
* @return a random KUID starting with the given prefix
*/
public static KUID createPrefxNodeID(KUID prefix, int depth) {
byte[] random = new byte[LENGTH];
GENERATOR.nextBytes(random);
return createPrefxNodeID(prefix, depth, random);
}
/**
* Creates a random ID with the specified byte prefix.
*
* @param prefix the fixed prefix bytes
* @param depth of the Bucket in the Trie
* @param random random bytes
* @return a random KUID starting with the given prefix
*/
private static KUID createPrefxNodeID(KUID prefix, int depth, byte[] random) {
depth++;
int length = depth/8;
System.arraycopy(prefix.id, 0, random, 0, length);
int bitsToCopy = depth % 8;
if (bitsToCopy != 0) {
// Mask has the low-order (8-bits) bits set
int mask = (1 << (8-bitsToCopy)) - 1;
int prefixByte = prefix.id[length];
int randByte = random[length];
random[length] = (byte) ((prefixByte & ~mask) | (randByte & mask));
}
return new KUID(random);
}
/**
* The default KeyAnalyzer for KUIDs.
*/
public static final KeyAnalyzer<KUID> KEY_ANALYZER = new KUIDKeyAnalyzer();
/**
* A <code>PatriciaTrie</code> <code>KeyAnalyzer</code> for <code>KUIDs</code>.
*/
private static class KUIDKeyAnalyzer implements KeyAnalyzer<KUID> {
private static final long serialVersionUID = 6412279289438108492L;
public int bitIndex(KUID key, int keyStart, int keyLength, KUID found, int foundStart, int foundLength) {
if (found == null) {
found = KUID.MINIMUM;
}
return key.bitIndex(found);
}
public int bitsPerElement() {
return 1;
}
public boolean isBitSet(KUID key, int keyLength, int bitIndex) {
return key.isBitSet(bitIndex);
}
public boolean isPrefix(KUID prefix, int offset, int length, KUID key) {
int end = offset + length;
for (int i = offset; i < end; i++) {
if (prefix.isBitSet(i) != key.isBitSet(i)) {
return false;
}
}
return true;
}
public int length(KUID key) {
return KUID.LENGTH;
}
public int compare(KUID o1, KUID o2) {
return o1.compareTo(o2);
}
}
}