/*
* (PD) 2003 The Bitzi Corporation Please see http://bitzi.com/publicdomain for
* more info.
*
* $Id: MerkleTree.java,v 1.3 2009/06/08 19:57:54 dsullivan Exp $
*/
package com.limegroup.gnutella.security;
import java.security.DigestException;
import java.security.MessageDigest;
import java.util.ArrayList;
/**
* Implementation of Merkle Tree hash algorithm, (using the approach as revised
* in December 2002, to add unique prefixes to leaf and node operations)
* <p>
* This class calculates the root of a MerkleTree, and keeps at most log(n) nodes
* (one for each tree level) in memory while doing so.
*/
public class MerkleTree extends MessageDigest {
private static final int BLOCKSIZE = 1024;
public static final int HASHSIZE = 24;
/** A Marker for the Stack. */
private static final byte[] MARKER = new byte[0];
/** 1024 byte buffer. */
private final byte[] buffer;
/** Buffer offset. */
private int bufferOffset;
/** Number of bytes hashed until now. */
private long byteCount;
/** Internal Tiger MD instance. */
private final MessageDigest internalDigest;
/** The List of Nodes */
private final ArrayList<byte[]> nodes;
/**
* Constructor.
*/
public MerkleTree(MessageDigest internalDigest) {
super("merkletree");
buffer = new byte[BLOCKSIZE];
bufferOffset = 0;
byteCount = 0;
nodes = new ArrayList<byte[]>();
this.internalDigest = internalDigest;
}
@Override
protected int engineGetDigestLength() {
return HASHSIZE;
}
@Override
protected void engineUpdate(byte in) {
byteCount += 1;
buffer[bufferOffset++] = in;
if (bufferOffset == BLOCKSIZE) {
blockUpdate();
bufferOffset = 0;
}
}
@Override
protected void engineUpdate(byte[] in, int offset, int length) {
byteCount += length;
nodes.ensureCapacity(log2Ceil(byteCount / BLOCKSIZE));
if (bufferOffset > 0) {
int remaining = BLOCKSIZE - bufferOffset;
System.arraycopy(in, offset, buffer, bufferOffset, remaining);
blockUpdate();
bufferOffset = 0;
length -= remaining;
offset += remaining;
}
while (length >= BLOCKSIZE) {
blockUpdate(in, offset, BLOCKSIZE);
length -= BLOCKSIZE;
offset += BLOCKSIZE;
}
if (length > 0) {
System.arraycopy(in, offset, buffer, 0, length);
bufferOffset = length;
}
}
@Override
protected byte[] engineDigest() {
byte[] hash = new byte[HASHSIZE];
try {
engineDigest(hash, 0, HASHSIZE);
} catch (DigestException e) {
return null;
}
return hash;
}
@Override
protected int engineDigest(byte[] buf, int offset, int len) throws DigestException {
if (len < HASHSIZE)
throw new DigestException();
// hash any remaining fragments
blockUpdate();
byte[] ret = collapse();
assert(ret != MARKER);
System.arraycopy(ret, 0, buf, offset, HASHSIZE);
engineReset();
return HASHSIZE;
}
/**
* Collapse whatever the tree is now to a root.
*/
private byte[] collapse() {
byte[] last = null;
for (int i = 0; i < nodes.size(); i++) {
byte[] current = nodes.get(i);
if (current == MARKER)
continue;
if (last == null)
last = current;
else {
internalDigest.reset();
internalDigest.update((byte) 1);
internalDigest.update(current);
internalDigest.update(last);
last = internalDigest.digest();
}
nodes.set(i, MARKER);
}
assert(last != null);
return last;
}
@Override
protected void engineReset() {
bufferOffset = 0;
byteCount = 0;
nodes.clear();
internalDigest.reset();
}
/**
* Method overrides MessageDigest.clone().
*
* @see java.security.MessageDigest#clone()
*/
@Override
public Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
protected void blockUpdate() {
blockUpdate(buffer, 0, bufferOffset);
}
/**
* Update the internal state with a single block of size 1024 (or less, in
* final block) from the internal buffer.
*/
protected void blockUpdate(byte [] buf, int pos, int len) {
internalDigest.reset();
internalDigest.update((byte) 0); // leaf prefix
internalDigest.update(buf, pos, len);
if ((len == 0) && (nodes.size() > 0))
return; // don't remember a zero-size hash except at very beginning
byte [] digest = internalDigest.digest();
push(digest);
}
private void push(byte[] data) {
if (!nodes.isEmpty()) {
for (int i = 0; i < nodes.size(); i++) {
byte[] node = nodes.get(i);
if (node == MARKER) {
nodes.set(i, data);
return;
}
internalDigest.reset();
internalDigest.update((byte) 1);
internalDigest.update(node);
internalDigest.update(data);
data = internalDigest.digest();
nodes.set(i, MARKER);
}
}
nodes.add(data);
}
// calculates the next n with 2^n > number
public static int log2Ceil(long number) {
int n = 0;
while (number > 1) {
number++; // for rounding up.
number >>>= 1;
n++;
}
return n;
}
}