package lbms.plugins.mldht.kad; import java.io.Serializable; import java.util.*; /** * @author Damokles * */ public class Key implements Comparable<Key>, Serializable { public static final class DistanceOrder implements Comparator<Key> { final Key target; public DistanceOrder(Key target) { this.target = target; } public int compare(Key o1, Key o2) { return target.distance(o1).compareTo(target.distance(o2)); } } private static final long serialVersionUID = -1180893806923345652L; public static final int SHA1_HASH_LENGTH = 20; private byte[] hash = new byte[SHA1_HASH_LENGTH]; /** * A Key in the DHT. * * Key's in the distributed hash table are just SHA-1 hashes. * Key provides all necesarry operators to be used as a value. */ private Key () { } /** * Clone constructor * * @param k Key to clone */ public Key (Key k) { System.arraycopy(k.hash, 0, hash, 0, SHA1_HASH_LENGTH); } /** * Creates a Key with this hash * * @param hash the SHA1 hash, has to be 20 bytes */ public Key (byte[] hash) { if (hash.length != SHA1_HASH_LENGTH) { throw new IllegalArgumentException( "Invalid Hash must be 20bytes, was: " + hash.length); } System.arraycopy(hash, 0, this.hash, 0, SHA1_HASH_LENGTH); } /* * (non-Javadoc) * * @see java.lang.Comparable#compareTo(java.lang.Object) */ public int compareTo (Key o) { for (int i = 0; i < hash.length; i++) { //needs & 0xFF since bytes are signed in Java //so we must convert to int to compare it unsigned if ((hash[i] & 0xFF) < (o.hash[i] & 0xFF)) { return -1; } else if ((hash[i] & 0xFF) > (o.hash[i] & 0xFF)) { return 1; } } return 0; } /* * (non-Javadoc) * * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals (Object obj) { if (obj instanceof Key) return Arrays.equals(hash, ((Key)obj).hash); return super.equals(obj); } /** * @return the hash */ public byte[] getHash () { return hash.clone(); } /* * (non-Javadoc) * * @see java.lang.Object#hashCode() */ @Override public int hashCode () { return (hash[0] ^ hash[1] ^ hash[2] ^ hash[3] ^ hash[4]) << 24 | (hash[5] ^ hash[6] ^ hash[7] ^ hash[8] ^ hash[9]) << 16 | (hash[10] ^ hash[11] ^ hash[12] ^ hash[13] ^ hash[14]) << 8 | (hash[15] ^ hash[16] ^ hash[17] ^ hash[18] ^ hash[19]); } /* (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString () { return toString(true); } public String toString(boolean nicePrint) { StringBuilder b = new StringBuilder(nicePrint ? 44 : 40); for (int i = 0; i < hash.length; i++) { if (nicePrint && i % 4 == 0 && i > 0) { b.append(' '); } int nibble = (hash[i] & 0xF0) >> 4; b.append((char)(nibble < 0x0A ? '0'+nibble : 'A'+nibble-10 )); nibble = hash[i] & 0x0F; b.append((char)(nibble < 0x0A ? '0'+nibble : 'A'+nibble-10 )); } return b.toString(); } /** * Generates a Key that has b equal bits in the beginning. * * @param b equal bits * @return A Key which has b equal bits with this Key. */ public Key createKeyWithDistance (int b) { // first generate a random one Key r = Key.createRandomKey(); byte[] data = r.getHash(); //need to map to the correct coordinates b = 159 - b; // before we hit bit b, everything needs to be equal to our_id int nb = b / 8; for (int i = 0; i < nb; i++) { data[i] = hash[i]; } // copy all bits of ob, until we hit the bit which needs to be different int ob = hash[nb]; for (int j = 0; j < b % 8; j++) { if (((0x80 >> j) & ob) != 0) { data[nb] |= (0x80 >> j); } else { data[nb] &= ~(0x80 >> j); } } // if the bit b is on turn it off else turn it on if (((0x80 >> (b % 8)) & ob) != 0) { data[nb] &= ~(0x80 >> (b % 8)); } else { data[nb] |= (0x80 >> (b % 8)); } return new Key(data); } /** * Returns the approximate distance of this key to the other key. * * Distance is simplified by returning the index of the first different Bit. * * @param id Key to compare to. * @return integer marking the different bits of the keys */ public int findApproxKeyDistance (Key id) { // XOR our id and the sender's ID Key d = Key.distance(id, this); // now use the first on bit to determine which bucket it should go in int bit_on = 0xFF; byte[] data_hash = d.getHash(); for (int i = 0; i < 20; i++) { // get the byte int b = data_hash[i] & 0xFF; // no bit on in this byte so continue if (b == 0) { continue; } for (int j = 0; j < 8; j++) { if ((b & (0x80 >> j)) != 0) { // we have found the bit bit_on = i * 8 + j; return 159 - bit_on; } } } return 0; } /** * Calculates the distance between two Keys. * * The distance is basically a XOR of both key hashes. * * @param x * @return new Key (this.hash ^ x.hash); */ public Key distance (Key x) { return distance(this, x); } /** * * @param Key to be checked * @return true if this key is a prefix of the provided key */ public boolean isPrefixOf(Key k) { List<Key> keys = new ArrayList<Key>(2); keys.add(this); keys.add(k); return getCommonPrefix(keys).equals(this); } /** * Calculates the distance between two Keys. * * The distance is basically a XOR of both key hashes. * * @param a * @param b * @return new Key (a.hash ^ b.hash); */ public static Key distance (Key a, Key b) { Key x = new Key(); for (int i = 0; i < a.hash.length; i++) { x.hash[i] = (byte) (a.hash[i] ^ b.hash[i]); } return x; } public static Key getCommonPrefix(Collection<Key> keys) { Key first = Collections.min(keys); Key last = Collections.max(keys); Key prefix = new Key(); byte[] newHash = prefix.hash; outer: for(int i=0;i<SHA1_HASH_LENGTH;i++) { if(first.hash[i] == last.hash[i]) { newHash[i] = first.hash[i]; continue; } // first differing byte newHash[i] = (byte)(first.hash[i] & last.hash[i]); for(int j=0;j<8;j++) { int mask = 0x80 >> j; // find leftmost differing bit and then zero out all following bits if(((first.hash[i] ^ last.hash[i]) & mask) != 0) { newHash[i] = (byte)(newHash[i] & ~(0xFF >> j)); break outer; } } } return prefix; } /** * Creates a random Key * * @return newly generated random Key */ public static Key createRandomKey () { Key x = new Key(); new Random().nextBytes(x.hash); return x; } }