/*
* A CCNx library test.
*
* Copyright (C) 2008, 2009, 2011 Palo Alto Research Center, Inc.
*
* This work is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License version 2 as published by the
* Free Software Foundation.
* This work 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 this program; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
package org.ccnx.ccn.test.security.crypto;
import java.util.Arrays;
import java.util.Random;
import org.bouncycastle.asn1.x509.DigestInfo;
import org.ccnx.ccn.impl.security.crypto.CCNDigestHelper;
import org.ccnx.ccn.impl.security.crypto.MerklePath;
import org.ccnx.ccn.impl.security.crypto.MerkleTree;
import org.ccnx.ccn.impl.support.Log;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
/**
* Test the low-level Merkle hash tree implementation.
*/
public class MerkleTreeTest {
protected static Random _rand = new Random(); // don't need SecureRandom
@BeforeClass
public static void setUpBeforeClass() throws Exception {
}
@Before
public void setUp() throws Exception {
}
@Test
public void testMerkleTree() throws Exception {
Log.info(Log.FAC_TEST, "Starting testMerkleTree");
int [] sizes = new int[]{128,256,512,4096};
try {
testTree(0, sizes[0], false);
Assert.fail("MerkleTree should throw an exception for tree sizes < 2.");
} catch (IllegalArgumentException e) {
// ok
}
try {
testTree(1, sizes[0], false);
Assert.fail("MerkleTree should throw an exception for tree sizes < 2.");
} catch (IllegalArgumentException e) {
// ok
}
System.out.println("Testing small trees.");
for (int i=2; i < 515; ++i) {
testTree(i,sizes[i%sizes.length],false);
}
System.out.println("Testing large trees.");
int [] nodecounts = new int[]{1000,1001,1025,1098,1536,1575,2053,5147,8900,9998,9999,10000};
for (int i=0; i < nodecounts.length; ++i) {
testTree(nodecounts[i],sizes[i%sizes.length],false);
}
Log.info(Log.FAC_TEST, "Completed testMerkleTree");
}
public static void testTree(int numLeaves, int nodeLength, boolean digest) throws Exception {
try {
byte [][] data = makeContent(numLeaves, nodeLength, digest);
testTree(data, numLeaves, digest);
} catch (Exception e) {
System.out.println("Building tree of " + numLeaves + " Nodes. Caught a " + e.getClass().getName() + " exception: " + e.getMessage());
throw e;
}
}
public static byte [][] makeContent(int numNodes, int nodeLength, boolean digest) {
byte [][] bufs = new byte[numNodes][];
byte [] tmpbuf = null;
if (digest)
tmpbuf = new byte[nodeLength];
int blocklen = (digest ? CCNDigestHelper.DEFAULT_DIGEST_LENGTH : nodeLength);
for (int i=0; i < numNodes; ++i) {
bufs[i] = new byte[blocklen];
if (digest) {
_rand.nextBytes(tmpbuf);
bufs[i] = CCNDigestHelper.digest(tmpbuf);
} else {
_rand.nextBytes(bufs[i]);
}
}
return bufs;
}
public static void testTree(byte [][] content, int count, boolean digest) {
// Generate a merkle tree. Verify each path for the content.
MerkleTree tree = new MerkleTree(content, digest, count, 0,
((count-1) >= 0) && ((count-1) < content.length) ? content[count-1].length : 0);
MerklePath [] paths = new MerklePath[count];
for (int i=0; i < count; ++i) {
paths[i] = tree.path(i);
//checkPath(tree, paths[i]);
byte [] root = paths[i].root(content[i],digest);
boolean result = Arrays.equals(root, tree.root());
if (!result) {
System.out.println("Constructed tree of " + count + " blocks (of " + content.length + "), numleaves: " +
tree.numLeaves() + " max pathlength: " + tree.maxDepth());
System.out.println("Path " + i + " verified for leaf " + paths[i].leafNodeIndex() + "? " + result);
}
Assert.assertTrue("Path " + i + " failed to verify.", result);
try {
byte [] encodedPath = paths[i].derEncodedPath();
DigestInfo info = CCNDigestHelper.digestDecoder(encodedPath);
MerklePath decoded = new MerklePath(info.getDigest());
if (!decoded.equals(paths[i])) {
System.out.println("Path " + i + " failed to encode and decode.");
Assert.fail("Path " + i + " failed to encode and decode.");
}
} catch (Exception e) {
System.out.println("Exception encoding path " + i + " :" + e.getClass().getName() + ": " + e.getMessage());
e.printStackTrace();
Assert.fail("Exception encoding path " + i + " :" + e.getClass().getName() + ": " + e.getMessage());
}
}
}
protected static void checkPath(MerkleTree tree, MerklePath path) {
// Check path against the tree, and see if it contains the hashes it should.
System.out.println("Checking path for nodeID: " + path.leafNodeIndex() + " path length: " + path.pathLength() + " num components: " + path.pathLength());
StringBuffer buf = new StringBuffer("Path nodes: ");
for (int i=0; i < path.pathLength(); ++i) {
buf.append(tree.getNodeIndex(path.entry(i)));
buf.append(" ");
}
System.out.println(buf.toString());
}
}