/** * @author Joshua Kissoon * @created 20140215 * @desc Represents a Kademlia Node ID */ package kademlia.node; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.Serializable; import java.math.BigInteger; import java.util.Arrays; import java.util.BitSet; import java.util.Random; import kademlia.message.Streamable; public class KademliaId implements Streamable, Serializable { public final transient static int ID_LENGTH = 160; private byte[] keyBytes; /** * Construct the NodeId from some string * * @param data The user generated key string */ public KademliaId(String data) { keyBytes = data.getBytes(); if (keyBytes.length != ID_LENGTH / 8) { throw new IllegalArgumentException("Specified Data need to be " + (ID_LENGTH / 8) + " characters long."); } } /** * Generate a random key */ public KademliaId() { keyBytes = new byte[ID_LENGTH / 8]; new Random().nextBytes(keyBytes); } /** * Generate the NodeId from a given byte[] * * @param bytes */ public KademliaId(byte[] bytes) { if (bytes.length != ID_LENGTH / 8) { throw new IllegalArgumentException("Specified Data need to be " + (ID_LENGTH / 8) + " characters long. Data Given: '" + new String(bytes) + "'"); } this.keyBytes = bytes; } /** * Load the NodeId from a DataInput stream * * @param in The stream from which to load the NodeId * * @throws IOException */ public KademliaId(DataInputStream in) throws IOException { this.fromStream(in); } public byte[] getBytes() { return this.keyBytes; } /** * @return The BigInteger representation of the key */ public BigInteger getInt() { return new BigInteger(1, this.getBytes()); } /** * Compares a NodeId to this NodeId * * @param o The NodeId to compare to this NodeId * * @return boolean Whether the 2 NodeIds are equal */ @Override public boolean equals(Object o) { if (o instanceof KademliaId) { KademliaId nid = (KademliaId) o; return this.hashCode() == nid.hashCode(); } return false; } @Override public int hashCode() { int hash = 7; hash = 83 * hash + Arrays.hashCode(this.keyBytes); return hash; } /** * Checks the distance between this and another NodeId * * @param nid * * @return The distance of this NodeId from the given NodeId */ public KademliaId xor(KademliaId nid) { byte[] result = new byte[ID_LENGTH / 8]; byte[] nidBytes = nid.getBytes(); for (int i = 0; i < ID_LENGTH / 8; i++) { result[i] = (byte) (this.keyBytes[i] ^ nidBytes[i]); } KademliaId resNid = new KademliaId(result); return resNid; } /** * Generates a NodeId that is some distance away from this NodeId * * @param distance in number of bits * * @return NodeId The newly generated NodeId */ public KademliaId generateNodeIdByDistance(int distance) { byte[] result = new byte[ID_LENGTH / 8]; /* Since distance = ID_LENGTH - prefixLength, we need to fill that amount with 0's */ int numByteZeroes = (ID_LENGTH - distance) / 8; int numBitZeroes = 8 - (distance % 8); /* Filling byte zeroes */ for (int i = 0; i < numByteZeroes; i++) { result[i] = 0; } /* Filling bit zeroes */ BitSet bits = new BitSet(8); bits.set(0, 8); for (int i = 0; i < numBitZeroes; i++) { /* Shift 1 zero into the start of the value */ bits.clear(i); } bits.flip(0, 8); // Flip the bits since they're in reverse order result[numByteZeroes] = (byte) bits.toByteArray()[0]; /* Set the remaining bytes to Maximum value */ for (int i = numByteZeroes + 1; i < result.length; i++) { result[i] = Byte.MAX_VALUE; } return this.xor(new KademliaId(result)); } /** * Counts the number of leading 0's in this NodeId * * @return Integer The number of leading 0's */ public int getFirstSetBitIndex() { int prefixLength = 0; for (byte b : this.keyBytes) { if (b == 0) { prefixLength += 8; } else { /* If the byte is not 0, we need to count how many MSBs are 0 */ int count = 0; for (int i = 7; i >= 0; i--) { boolean a = (b & (1 << i)) == 0; if (a) { count++; } else { break; // Reset the count if we encounter a non-zero number } } /* Add the count of MSB 0s to the prefix length */ prefixLength += count; /* Break here since we've now covered the MSB 0s */ break; } } return prefixLength; } /** * Gets the distance from this NodeId to another NodeId * * @param to * * @return Integer The distance */ public int getDistance(KademliaId to) { /** * Compute the xor of this and to * Get the index i of the first set bit of the xor returned NodeId * The distance between them is ID_LENGTH - i */ return ID_LENGTH - this.xor(to).getFirstSetBitIndex(); } @Override public void toStream(DataOutputStream out) throws IOException { /* Add the NodeId to the stream */ out.write(this.getBytes()); } @Override public final void fromStream(DataInputStream in) throws IOException { byte[] input = new byte[ID_LENGTH / 8]; in.readFully(input); this.keyBytes = input; } public String hexRepresentation() { /* Returns the hex format of this NodeId */ BigInteger bi = new BigInteger(1, this.keyBytes); return String.format("%0" + (this.keyBytes.length << 1) + "X", bi); } @Override public String toString() { return this.hexRepresentation(); } }