/*
* This file is part of mlDHT.
*
* mlDHT 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.
*
* mlDHT 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 mlDHT. If not, see <http://www.gnu.org/licenses/>.
*/
package lbms.plugins.mldht.kad;
import static the8472.utils.Arrays.mismatch;
import java.util.Collection;
import java.util.Collections;
public class Prefix extends Key {
/**
* identifies the first bit of a key that has to be equal to be considered as covered by this prefix
* -1 = prefix matches whole keyspace
* 0 = 0th bit must match
* 1 = ...
*/
int depth = -1;
public static final Prefix WHOLE_KEYSPACE = new Prefix();
public Prefix() {
super();
}
public Prefix(Prefix p) {
super(p);
depth = p.depth;
}
public Prefix(Key k, int depth) {
copyBits(k, this, depth);
this.depth = depth;
}
/**
*
* @param KeyTest to be checked
* @return true if this Prefix covers the provided key
*/
public boolean isPrefixOf(Key k)
{
return bitsEqual(this, k, depth);
}
public Prefix splitPrefixBranch(boolean highBranch) {
Prefix branch = new Prefix(this);
int branchDepth = ++branch.depth;
if(highBranch)
branch.hash[branchDepth / 8] |= 0x80 >> (branchDepth % 8);
else
branch.hash[branchDepth / 8] &= ~(0x80 >> (branchDepth % 8));
return branch;
}
public Key first() {
return new Key(this);
}
public Key last() {
Key trailingBits = new Prefix(Key.MAX_KEY, depth).distance(Key.MAX_KEY);
return this.distance(trailingBits);
}
public Prefix getParentPrefix() {
if(depth == -1)
return this;
Prefix parent = new Prefix(this);
int oldDepth = parent.depth--;
// set last bit to zero
parent.hash[oldDepth / 8] &= ~(0x80 >> (oldDepth % 8));
return parent;
}
public boolean isSiblingOf(Prefix otherPrefix)
{
if(depth != otherPrefix.depth)
return false;
return bitsEqual(this, otherPrefix, depth-1);
}
/**
* @return true if the first bits up to the Nth bit of both keys are equal
*
* <pre>
* n = -1 => no bits have to match
* n = 0 => byte 0, MSB has to match
* </pr>
*/
private static boolean bitsEqual(Key k1, Key k2, int n)
{
if(n < 0)
return true;
byte[] h1 = k1.hash;
byte[] h2 = k2.hash;
int lastToCheck = n >>> 3;
int mmi = mismatch(h1, h2);
int diff = (h1[lastToCheck] ^ h2[lastToCheck]) & 0xff;
boolean lastByteDiff = (diff & (0xff80 >>> (n & 0x07))) == 0;
return mmi == lastToCheck ? lastByteDiff : Integer.compareUnsigned(mmi, lastToCheck) > 0;
}
private static void copyBits(Key source, Key destination, int depth)
{
if(depth < 0)
return;
byte[] data = destination.hash;
// copy over all complete bytes
for (int i = 0; i < depth / 8; i++)
data[i] = source.hash[i];
int idx = depth / 8;
int mask = 0xFF80 >> depth % 8;
// mask out the part we have to copy over from the last prefix byte
data[idx] &= ~mask;
// copy the bits from the last byte
data[idx] |= source.hash[idx] & mask;
}
public int getDepth() {
return depth;
}
@Override
public String toString() {
if(depth == -1)
return "all";
StringBuilder builder = new StringBuilder(depth+3);
for(int i=0;i<=depth;i++)
builder.append((hash[i/8] & (0x80 >> (i % 8))) != 0 ? '1' : '0');
builder.append("...");
return builder.toString();
}
/**
* Generates a random Key that has falls under this prefix
*/
public Key createRandomKeyFromPrefix() {
// first generate a random one
Key key = Key.createRandomKey();
copyBits(this, key, depth);
return key;
}
/*
public Key lowestKey() {
Key k = new Key();
copyBits(this, k, depth);
return k;
}
public Key highestKey() {
Key k = new Key(Key.MAX_KEY);
copyBits(this, k, depth);
return k;
}*/
public static Prefix getCommonPrefix(Collection<Key> keys)
{
if(keys.isEmpty())
throw new IllegalArgumentException("keys cannot be empty");
Key first = Collections.min(keys);
Key last = Collections.max(keys);
Prefix prefix = new Prefix();
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];
prefix.depth += 8;
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;
}
prefix.depth++;
}
}
return prefix;
}
public static void main(String[] args) {
Prefix p = new Prefix();
p.hash[0] = (byte) 0x30;
p.depth = 3;
Key k = new Key();
k.hash[0] = (byte) 0x37;
System.out.println(p);
System.out.println(p.isPrefixOf(k));
}
}