/*
* A CCNx library test.
*
* Copyright (C) 2010-2012 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;
import java.security.DigestOutputStream;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Random;
import org.ccnx.ccn.CCNHandle;
import org.ccnx.ccn.config.UserConfiguration;
import org.ccnx.ccn.impl.CCNNetworkManager;
import org.ccnx.ccn.impl.CCNFlowControl.SaveType;
import org.ccnx.ccn.impl.security.crypto.CCNDigestHelper;
import org.ccnx.ccn.impl.security.crypto.util.SignatureHelper;
import org.ccnx.ccn.impl.support.Tuple;
import org.ccnx.ccn.io.NoMatchingContentFoundException;
import org.ccnx.ccn.io.NullOutputStream;
import org.ccnx.ccn.io.content.CCNStringObject;
import org.ccnx.ccn.profiles.SegmentationProfile;
import org.ccnx.ccn.profiles.VersioningProfile;
import org.ccnx.ccn.protocol.ContentName;
import org.ccnx.ccn.protocol.ContentObject;
import org.ccnx.ccn.protocol.Interest;
import org.ccnx.ccn.protocol.Signature;
import org.junit.BeforeClass;
import org.junit.Test;
/**
* This is not a unit test designed to verify functionality.
* Instead, this test times some operations for basic benchmarking.
* @author jthornto
*
*/
public class BenchmarkTest {
public static final int NUM_ITER = 1000;
public static final int NUM_KEYGEN = 100; // Key generation is really slow
public static final double NanoToMilli = 1000000.0d;
public static final double NanoToSec = 1000000000000.0d;
public static CCNTestHelper testHelper = new CCNTestHelper(BenchmarkTest.class);
public static CCNHandle handle;
public static ContentName testName;
public static byte[] shortPayload;
public static byte[] longPayload;
public static byte[] veryLongPayload;
public static byte [][] payloads;
// Need to benchmark multiple key lengths
public static final int [] keyLengths = new int[]{512, 1024, 2048};
// Need to benchmark multiple digest algorithms. MD5 not used for signing,
// but is used for non-security critical applications.
public static final String [] digestAlgorithms = new String[]{"MD5", "SHA1", CCNDigestHelper.DEFAULT_DIGEST_ALGORITHM};
public static final int LONG_LENGTH = 1000;
public static final int VERY_LONG_LENGTH = 4096; // our actual packet length
public static ContentObject [] contentObjects;
public static ContentObject [] unsignedContentObjects;
// Default algorithm, add ECC later
public static KeyPair[] keyPairs = new KeyPair[keyLengths.length];
public static NumberFormat format = DecimalFormat.getNumberInstance();
static abstract class Operation<T extends Object,U extends Object> {
abstract Object execute(T input, U parameter) throws Exception;
int size(T input) {
if (null == input) {
return -1;
} else if (input instanceof byte[]) {
return ((byte[])input).length;
} else if (input instanceof ContentObject) {
return ((ContentObject)input).content().length;
} else {
throw new RuntimeException("Unsupported input type " + input.getClass());
}
}
public void runBenchmark(String desc, T input, U parameter) throws Exception {
runBenchmark(NUM_ITER, desc, input, parameter);
}
public void runBenchmark(int count, String desc, T input, U parameter) throws Exception {
long start = System.nanoTime();
execute(input, parameter);
long dur = System.nanoTime() - start;
//System.out.println("Start " + start + " dur " + dur);
int size = size(input);
System.out.println("Initial time to " + desc + (size >= 0 ? " (payload " + size(input) + " bytes)" : "") +
" = " + dur/NanoToMilli + " ms.");
start = System.nanoTime();
for (int i=0; i<count; i++) {
execute(input, parameter);
}
dur = System.nanoTime() - start;
System.out.println("Avg. to " + desc + " (" + count + " iterations) = " +
dur/count/NanoToMilli + " ms. (" +
format.format(NanoToSec/dur) + " operations/sec)");
}
}
@BeforeClass
public static void setUpBeforeClass() throws Exception {
ContentName namespace = testHelper.getTestNamespace("benchmarkTest");
testName = new ContentName(namespace, "BenchmarkObject");
testName = VersioningProfile.addVersion(testName);
shortPayload = ("this is sample segment content").getBytes();
longPayload = new byte[LONG_LENGTH];
Random rnd = new Random();
rnd.nextBytes(longPayload);
veryLongPayload = new byte[VERY_LONG_LENGTH];
rnd.nextBytes(veryLongPayload);
payloads = new byte [][]{shortPayload, longPayload, veryLongPayload};
contentObjects = new ContentObject[payloads.length];
unsignedContentObjects = new ContentObject[payloads.length];
// Should have content objects signed by multiple key types...
ContentName segmentName = SegmentationProfile.segmentName(testName, 0);
for (int i=0; i < payloads.length; ++i) {
contentObjects[i] = ContentObject.buildContentObject(segmentName, payloads[i], null, null, SegmentationProfile.getSegmentNumberNameComponent(0));
unsignedContentObjects[i] = new ContentObject(contentObjects[i].name(), contentObjects[i].signedInfo(), contentObjects[i].content(), (Signature) null);
}
final KeyPairGenerator kpg = KeyPairGenerator.getInstance(UserConfiguration.defaultKeyAlgorithm());
for (int i=0; i < keyLengths.length; ++i) {
kpg.initialize(keyLengths[i]);
keyPairs[i] = kpg.generateKeyPair();
}
format.setMaximumFractionDigits(3);
handle = CCNHandle.open();
System.out.println("Benchmark Test starting on " + System.getProperty("os.name"));
}
@Test
public void testDigest() throws Exception {
System.out.println("==== Digests");
Operation<byte[],String> digest = new Operation<byte[],String>() {
Object execute(byte[] input, String algorithm) throws Exception {
MessageDigest md = MessageDigest.getInstance(algorithm);
md.update(input);
return md.digest();
}
};
for (int i=0; i < digestAlgorithms.length; ++i) {
System.out.println("--- Raw = digest only of byte[] using " + digestAlgorithms[i]);
for (int j=0; j<payloads.length; ++j) {
digest.runBenchmark("raw digest (" + payloads[j].length + " bytes)",
payloads[j], digestAlgorithms[i]);
}
System.out.println("");
}
Operation<ContentObject, String> digestObj = new Operation<ContentObject, String>() {
Object execute(ContentObject input, String algorithm) throws Exception {
MessageDigest md = MessageDigest.getInstance(algorithm);
DigestOutputStream dos = new DigestOutputStream(new NullOutputStream(), md);
input.encode(dos);
return md.digest();
}
};
for (int i=0; i < digestAlgorithms.length; ++i) {
System.out.println("--- Raw = digest of contentObject using " + digestAlgorithms[i]);
for (int j=0; j<contentObjects.length; ++j) {
digestObj.runBenchmark("ContentObject digest (content " + contentObjects[j].contentLength() + " bytes) ",
contentObjects[j], digestAlgorithms[i]);
}
System.out.println("");
}
}
@Test
public void testEncode() throws Exception {
System.out.println("==== Encoding");
Operation<ContentObject, String> encodeObj = new Operation<ContentObject, String>() {
Object execute(ContentObject input, String codec) throws Exception {
return input.encode(codec);
}
};
for (int j=0; j<contentObjects.length; ++j) {
encodeObj.runBenchmark("ContentObject encode (content " + contentObjects[j].contentLength() + " bytes) ",
contentObjects[j], null);
}
System.out.println("");
Operation<ContentObject, Object> prepareObj = new Operation<ContentObject, Object>() {
Object execute(ContentObject input, Object ignored) throws Exception {
return ContentObject.prepareContent(input.name(), input.signedInfo(), input.content());
}
};
System.out.println("Prepare content: perform the encoding steps necessary for signing:");
for (int j=0; j<contentObjects.length; ++j) {
prepareObj.runBenchmark("ContentObject prepareDigest (content " + contentObjects[j].contentLength() + " bytes) ",
contentObjects[j], null);
}
System.out.println("");
}
@Test
public void testRawSigning() throws Exception {
// We need to be able to benchmark signing using multiple algorithms;
// probably should make this depend on digest algorithm as well.
// For right now, just parameterize on key size.
// Add ECC once we can test for its presence
Operation<byte[], Tuple<String,PrivateKey>> sign = new Operation<byte[], Tuple<String,PrivateKey>>() {
Object execute(byte[] input, Tuple<String,PrivateKey> signingParams) throws Exception {
return SignatureHelper.sign(signingParams.first(), input, signingParams.second());
}
};
// Need to handle multiple length of key, and at least SHA1 and SHA256
for (int i = 1; i < digestAlgorithms.length; ++i) {
// skip MD-5
System.out.println("==== PK Signing: Digest: " + digestAlgorithms[i]);
for (int j=0; j < keyPairs.length; ++j) {
System.out.println("======= " + keyLengths[j] + "-bit " + keyPairs[j].getPublic().getAlgorithm() + " Key with " + digestAlgorithms[i] + ":");
for (int k=0; k < payloads.length; ++k) {
sign.runBenchmark("sign " + payloads[k].length + " bytes ",
payloads[k], new Tuple<String,PrivateKey>(digestAlgorithms[i],keyPairs[j].getPrivate()));
}
System.out.println("");
}
}
System.out.println("");
Operation<Tuple<byte[],byte[]>, Tuple<String,PublicKey>> verify =
new Operation<Tuple<byte[],byte[]>, Tuple<String,PublicKey>>() {
Object execute(Tuple<byte[],byte[]> input, Tuple<String,PublicKey> verifyParams) throws Exception {
return SignatureHelper.verify(input.first(), input.second(),
verifyParams.first(), verifyParams.second());
}
int size(Tuple<byte[], byte[]> input) {
return input.first().length;
}
};
for (int i = 1; i < digestAlgorithms.length; ++i) {
// skip MD-5
System.out.println("==== PK Verifying: Digest: " + digestAlgorithms[i]);
for (int j=0; j < keyPairs.length; ++j) {
System.out.println("======= " + keyLengths[j] + "-bit " + keyPairs[j].getPublic().getAlgorithm() + " Key with " + digestAlgorithms[i] + ":");
for (int k=0; k < payloads.length; ++k) {
byte [] signature = (byte[])sign.execute(payloads[k], new Tuple<String,PrivateKey>(digestAlgorithms[i],keyPairs[j].getPrivate()));
verify.runBenchmark("verify " + payloads[k].length + " bytes ",
new Tuple<byte [], byte[]>(payloads[k],signature),
new Tuple<String,PublicKey>(digestAlgorithms[i],keyPairs[j].getPublic()));
}
System.out.println("");
}
}
System.out.println("");
}
@Test
public void testObjectSigning() throws Exception {
// We need to be able to benchmark signing using multiple algorithms;
// probably should make this depend on digest algorithm as well.
// For right now, just parameterize on key size.
// Add ECC once we can test for its presence
Operation<ContentObject, Tuple<String,PrivateKey>> objSign = new Operation<ContentObject, Tuple<String,PrivateKey>>() {
Object execute(ContentObject input, Tuple<String,PrivateKey> signingParams) throws Exception {
input.setSignature(null); // avoid warning
input.sign(signingParams.first(), signingParams.second());
return null;
}
};
Operation<ContentObject, PublicKey> objVerify =
new Operation<ContentObject, PublicKey>() {
Object execute(ContentObject input, PublicKey publicKey) throws Exception {
return input.verify(publicKey);
}
int size(ContentObject input) {
return input.contentLength();
}
};
// Need to handle multiple length of key, and at least SHA1 and SHA256
for (int i = 1; i < digestAlgorithms.length; ++i) {
// skip MD-5
System.out.println("==== PK Object Signing/Verifying: Digest: " + digestAlgorithms[i]);
for (int j=0; j < keyPairs.length; ++j) {
System.out.println("======= " + keyLengths[j] + "-bit " + keyPairs[j].getPublic().getAlgorithm() + " Key:");
for (int k=0; k < unsignedContentObjects.length; ++k) {
objSign.runBenchmark("sign " + unsignedContentObjects[k].contentLength() + " bytes ",
unsignedContentObjects[k],
new Tuple<String,PrivateKey>(digestAlgorithms[i],keyPairs[j].getPrivate()));
objVerify.runBenchmark("verify " + unsignedContentObjects[k].contentLength() + " bytes ",
unsignedContentObjects[k], keyPairs[j].getPublic());
}
System.out.println("");
}
}
System.out.println("");
}
@Test
public void testKeyGen() throws Exception {
Operation<Object, Tuple<KeyPairGenerator, Integer>> genpair = new Operation<Object, Tuple<KeyPairGenerator, Integer>>() {
Object execute(Object input, Tuple<KeyPairGenerator,Integer> keyGenParams) throws Exception {
KeyPairGenerator kpg = keyGenParams.first();
kpg.initialize(keyGenParams.second());
KeyPair userKeyPair = kpg.generateKeyPair();
return userKeyPair;
}
};
// Do ECC as well, once we can test for presence of ECC.
final KeyPairGenerator kpg = KeyPairGenerator.getInstance(UserConfiguration.defaultKeyAlgorithm());
for (int i = 0; i < keyLengths.length; ++i) {
System.out.println("==== Key Generation: " + keyLengths[i] + "-bit " + UserConfiguration.defaultKeyAlgorithm() + " key.");
genpair.runBenchmark(NUM_KEYGEN, "generate keypair", null, new Tuple<KeyPairGenerator, Integer>(kpg, keyLengths[i]));
}
}
@Test
public void testCcndRetrieve() throws Exception {
// Floss some content into ccnd
ContentName dataPrefix = testHelper.getTestNamespace("TestCcndRetrieve");
Flosser floss = new Flosser(dataPrefix);
CCNStringObject so = new CCNStringObject(dataPrefix, "This is the value", SaveType.RAW, handle);
so.save();
ContentName name = so.getVersionedName();
so.close();
floss.stop();
// Now that content is in local ccnd, we can benchmark retrieval of one content item
Operation<Interest, Object> getcontent = new Operation<Interest, Object>() {
Object execute(Interest interest, Object ignored) throws Exception {
// Note as of this writing, interest refresh was PERIOD*2 with no constant in net mgr
// We will use PERIOD for now, as we want to be sure to avoid refreshes and this should be fast.
ContentObject result = handle.get(interest, CCNNetworkManager.PERIOD);
// Make sure to throw exception if we get nothing back so this doesn't just
// look like a long successful run.
if (null == result) {
throw new NoMatchingContentFoundException("timeout on get for " + interest.name());
}
return null;
}
int size(Interest interest) {
return -1;
}
};
Interest interest = new Interest(name);
System.out.println("==== Single data retrieval from ccnd: " + name);
getcontent.runBenchmark("retrieve data", interest, null);
}
}