/*
* (PD) 2003 The Bitzi Corporation Please see http://bitzi.com/publicdomain for
* more info.
*
* $Id: TigerTree.java,v 1.8 2005/12/08 21:12:15 rkapsi Exp $
*/
package com.limegroup.gnutella.security;
import java.security.DigestException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Provider;
import java.security.Security;
import java.util.ArrayList;
import com.limegroup.gnutella.Assert;
import com.limegroup.gnutella.ErrorService;
import com.limegroup.gnutella.util.CommonUtils;
/**
* Implementation of THEX tree hash algorithm, with Tiger as the internal
* algorithm (using the approach as revised in December 2002, to add unique
* prefixes to leaf and node operations)
*
* For simplicity, calculates one entire generation before starting on the
* next. A more space-efficient approach would use a stack, and calculate each
* node as soon as its children ara available.
*/
public class TigerTree extends MessageDigest {
private static final int BLOCKSIZE = 1024;
private static final int HASHSIZE = 24;
// There is a bug in Jaguar's Java 1.4 implementation. Thus
// use Cryptix instead which doesn't expose the bug.
private static final boolean USE_CRYPTIX = CommonUtils.isJaguarOrAbove();
/**
* Set up the CryptixCrypto provider if we're on
* a platform that requires it.
*/
static {
if(USE_CRYPTIX) {
// Use reflection to load the Cryptix Provider.
// It's safest that way (since we don't want to include
// the cryptix jar on all installations, and Java
// may try to load the class otherwise).
try {
Class clazz =
Class.forName("cryptix.jce.provider.CryptixCrypto");
Object o = clazz.newInstance();
Security.addProvider((Provider)o);
} catch(ClassNotFoundException e) {
ErrorService.error(e);
} catch(IllegalAccessException e) {
ErrorService.error(e);
} catch(InstantiationException e) {
ErrorService.error(e);
} catch(ExceptionInInitializerError e) {
ErrorService.error(e);
} catch(SecurityException e) {
ErrorService.error(e);
} catch(ClassCastException e) {
ErrorService.error(e);
}
}
}
/** 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 MessageDigest tiger;
/** The List of Nodes */
private ArrayList nodes;
/**
* Constructor
*/
public TigerTree() {
super("tigertree");
buffer = new byte[BLOCKSIZE];
bufferOffset = 0;
byteCount = 0;
nodes = new ArrayList();
if(USE_CRYPTIX) {
try {
tiger = MessageDigest.getInstance("Tiger", "CryptixCrypto");
} catch(NoSuchAlgorithmException nsae) {
tiger = new Tiger();
} catch(NoSuchProviderException nspe) {
tiger = new Tiger();
}
} else
tiger = new Tiger();
}
protected int engineGetDigestLength() {
return HASHSIZE;
}
protected void engineUpdate(byte in) {
byteCount += 1;
buffer[bufferOffset++] = in;
if (bufferOffset == BLOCKSIZE) {
blockUpdate();
bufferOffset = 0;
}
}
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;
}
}
protected byte[] engineDigest() {
byte[] hash = new byte[HASHSIZE];
try {
engineDigest(hash, 0, HASHSIZE);
} catch (DigestException e) {
return null;
}
return hash;
}
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.that(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 = (byte[]) nodes.get(i);
if (current == MARKER)
continue;
if (last == null)
last = current;
else {
tiger.reset();
tiger.update((byte)1);
tiger.update(current);
tiger.update(last);
last = tiger.digest();
}
nodes.set(i,MARKER);
}
Assert.that(last != null);
return last;
}
protected void engineReset() {
bufferOffset = 0;
byteCount = 0;
nodes = new ArrayList();
tiger.reset();
}
/**
* Method overrides MessageDigest.clone()
*
* @see java.security.MessageDigest#clone()
*/
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) {
tiger.reset();
tiger.update((byte) 0); // leaf prefix
tiger.update(buf, pos, len);
if ((len == 0) && (nodes.size() > 0))
return; // don't remember a zero-size hash except at very beginning
byte [] digest = tiger.digest();
push(digest);
}
private void push(byte [] data) {
if (!nodes.isEmpty()) {
for (int i = 0; i < nodes.size(); i++) {
byte[] node = (byte[]) nodes.get(i);
if (node == MARKER) {
nodes.set(i,data);
return;
}
tiger.reset();
tiger.update((byte)1);
tiger.update(node);
tiger.update(data);
data = tiger.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;
}
}