import Iakin's public domain mods plus some additional ones (support for the resource 'jbigi' anywhere in the classpath, java 1.3 support, docs)

This commit is contained in:
jrandom
2004-08-21 09:11:10 +00:00
committed by zzz
parent 9ea6eed22f
commit 75ca438f2f

View File

@@ -8,22 +8,29 @@ package net.i2p.util;
*
*/
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigInteger;
import java.net.URL;
import java.security.SecureRandom;
import java.util.Random;
import java.security.SecureRandom;
import java.net.URL;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.File;
import freenet.support.CPUInformation.AMDCPUInfo;
import freenet.support.CPUInformation.CPUID;
import freenet.support.CPUInformation.CPUInfo;
import freenet.support.CPUInformation.IntelCPUInfo;
import freenet.support.CPUInformation.UnknownCPUException;
/**
* <p>BigInteger that takes advantage of the jbigi library for the modPow operation,
* which accounts for a massive segment of the processing cost of asymmetric
* crypto. The jbigi library itself is basically just a JNI wrapper around the
* crypto. It also takes advantage of the jbigi library for converting a BigInteger
* value to a double. Sun's implementation of the 'doubleValue()' method is _very_ lousy.
*
* The jbigi library itself is basically just a JNI wrapper around the
* GMP library - a collection of insanely efficient routines for dealing with
* big numbers.</p>
*
@@ -37,36 +44,27 @@ import java.util.Random;
* <p>If jbigi.enable is set to false, this class won't even attempt to use the
* native library, but if it is set to true (or is not specified), it will first
* check the platform specific library path for the "jbigi" library, as defined by
* {@link java.lang.Runtime#loadLibrary} - e.g. C:\windows\jbigi.dll or /lib/libjbigi.so.
* If that fails, it reviews the jbigi.impl environment property - if that is set,
* it checks all of the components in the CLASSPATH for the file specified and
* attempts to load it as the native library. If jbigi.impl is not set, if there
* is no matching resource, or if that resource is not a valid OS/architecture
* specific library, the NativeBigInteger will revert to using the pure java
* implementation.</p>
* {@link Runtime#loadLibrary} - e.g. C:\windows\jbigi.dll or /lib/libjbigi.so, as
* well as the CLASSPATH for a resource named 'jbigi'. If that fails, it reviews
* the jbigi.impl environment property - if that is set, it checks all of the
* components in the CLASSPATH for the file specified and attempts to load it as
* the native library. If jbigi.impl is not set, it uses the jcpuid library
* described below. If there is still no matching resource, or if that resource
* is not a valid OS/architecture specific library, the NativeBigInteger will
* revert to using the pure java implementation.</p>
*
* <p>That means <b>NativeBigInteger will not attempt to guess the correct
* platform/OS/whatever</b> - applications using this class should define that
* property prior to <i>referencing</i> the NativeBigInteger (or before loading
* the JVM, of course). Alternately, people with custom built jbigi implementations
* in their OS's standard search path (LD_LIBRARY_PATH, etc) needn't bother.</p>
*
* <p>One way to deploy the native library is to create a jbigi.jar file containing
* all of the native implementations with filenames such as "win-athlon", "linux-p2",
* "freebsd-sparcv4", where those files are the OS specific libraries (the contents of
* the DLL or .so file built for those OSes / architectures). The user would then
* simply specify -Djbigi.impl=win-athlon and this component would pick up that
* library.</p>
*
* <p>Another way is to create a seperate jbigi.jar file for each platform containing
* one file - "native", where that file is the OS / architecture specific library
* implementation, as above. This way the user would download the correct jbigi.jar
* (and not all of the libraries for platforms/OSes they don't need) and would specify
* -Djbigi.impl=native.</p>
* <p>When attempting to load the native implementation as a resource from the CLASSPATH,
* the NativeBigInteger will make use of the jcpuid component which runs some assembly
* code to determine the current CPU implementation, such as "pentium4" or "k623".
* We then use that, combined with the OS, to build an optimized resource name - e.g.
* "net/i2p/util/libjbigi-freebsd-pentium4.so" or "net/i2p/util/jbigi-windows-k623.dll".
* If that resource exists, we use it. If it doesn't (or the jcpuid component fails),
* we try a generic native implementation using "none" for the CPU (ala
* "net/i2p/util/jbigi-windows-none.dll").</p>
*
* <p>Running this class by itself does a basic unit test and benchmarks the
* NativeBigInteger.modPow vs. the BigInteger.modPow by running a 2Kbit op 100
* times. At the end, if the native implementation is loaded this will output
* NativeBigInteger.modPow/doubleValue vs. the BigInteger.modPow/doubleValue by running a 2Kbit op 100
* times. At the end of each test, if the native implementation is loaded this will output
* something like:</p>
* <pre>
* native run time: 6090ms (60ms each)
@@ -85,7 +83,7 @@ import java.util.Random;
* </pre>
*
*/
public class NativeBigInteger extends BigInteger {
public class NativeBigInteger_new extends BigInteger {
/** did we load the native lib correctly? */
private static boolean _nativeOk = false;
/**
@@ -95,60 +93,149 @@ public class NativeBigInteger extends BigInteger {
*/
private static final boolean _doLog = true;
private static String DEFAULT_REF_FILENAME = "jbigi.cfg";
private final static String JBIGI_OPTIMIZATION_K6 = "k6";
private final static String JBIGI_OPTIMIZATION_K6_2 = "k62";
private final static String JBIGI_OPTIMIZATION_K6_3 = "k63";
private final static String JBIGI_OPTIMIZATION_ATHLON = "athlon";
private final static String JBIGI_OPTIMIZATION_PENTIUM = "pentium";
private final static String JBIGI_OPTIMIZATION_PENTIUMMMX = "pentiummmx";
private final static String JBIGI_OPTIMIZATION_PENTIUM2 = "pentium2";
private final static String JBIGI_OPTIMIZATION_PENTIUM3 = "pentium3";
private final static String JBIGI_OPTIMIZATION_PENTIUM4 = "pentium4";
private final static String sCPUType; //The CPU Type to optimize for (one of the above strings)
static {
sCPUType = resolveCPUType();
loadNative();
}
/** Tries to resolve the best type of CPU that we have an optimized jbigi-dll/so for.
* @return A string containing the CPU-type or null if CPU type is unknown
*/
private static String resolveCPUType() {
try {
CPUInfo c = CPUID.getInfo();
if (c instanceof AMDCPUInfo) {
AMDCPUInfo amdcpu = (AMDCPUInfo) c;
if (amdcpu.IsAthlonCompatible())
return JBIGI_OPTIMIZATION_ATHLON;
if (amdcpu.IsK6_3_Compatible())
return JBIGI_OPTIMIZATION_K6_3;
if (amdcpu.IsK6_2_Compatible())
return JBIGI_OPTIMIZATION_K6_2;
if (amdcpu.IsK6Compatible())
return JBIGI_OPTIMIZATION_K6;
} else {
if (c instanceof IntelCPUInfo) {
IntelCPUInfo intelcpu = (IntelCPUInfo) c;
if (intelcpu.IsPentium4Compatible())
return JBIGI_OPTIMIZATION_PENTIUM4;
if (intelcpu.IsPentium3Compatible())
return JBIGI_OPTIMIZATION_PENTIUM3;
if (intelcpu.IsPentium2Compatible())
return JBIGI_OPTIMIZATION_PENTIUM2;
if (intelcpu.IsPentiumMMXCompatible())
return JBIGI_OPTIMIZATION_PENTIUMMMX;
if (intelcpu.IsPentiumCompatible())
return JBIGI_OPTIMIZATION_PENTIUM;
}
}
return null;
} catch (UnknownCPUException e) {
return null; //TODO: Log something here maybe..
}
}
/**
* calculate (base ^ exponent) % modulus.
* @param base big endian twos complement representation of the base (but it must be positive)
* @param exponent big endian twos complement representation of the exponent
* @param modulus big endian twos complement representation of the modulus
*
* @param base
* big endian twos complement representation of the base (but it must be positive)
* @param exponent
* big endian twos complement representation of the exponent
* @param modulus
* big endian twos complement representation of the modulus
* @return big endian twos complement representation of (base ^ exponent) % modulus
*/
public native static byte[] nativeModPow(byte base[], byte exponent[], byte modulus[]);
public NativeBigInteger(byte val[]) {
/**
* Converts a BigInteger byte-array to a 'double'
* @param ba Big endian twos complement representation of the BigInteger to convert to a double
* @return The plain double-value represented by 'ba'
*/
public native static double nativeDoubleValue(byte ba[]);
private byte[] cachedBa;
public NativeBigInteger_new(byte[] val) {
super(val);
}
public NativeBigInteger(int signum, byte magnitude[]) {
public NativeBigInteger_new(int signum, byte[] magnitude) {
super(signum, magnitude);
}
public NativeBigInteger(int bitlen, int certainty, Random rnd) {
public NativeBigInteger_new(int bitlen, int certainty, Random rnd) {
super(bitlen, certainty, rnd);
}
public NativeBigInteger(int numbits, Random rnd) {
public NativeBigInteger_new(int numbits, Random rnd) {
super(numbits, rnd);
}
public NativeBigInteger(String val) {
public NativeBigInteger_new(String val) {
super(val);
}
public NativeBigInteger(String val, int radix) {
public NativeBigInteger_new(String val, int radix) {
super(val, radix);
}
/**Creates a new NativeBigInteger with the same value
* as the supplied BigInteger. Warning!, not very efficent
*/
public NativeBigInteger_new(BigInteger integer) {
//Now, why doesn't sun provide a constructor
//like this one in BigInteger?
this(integer.toByteArray());
}
public BigInteger modPow(BigInteger exponent, BigInteger m) {
if (_nativeOk)
return new NativeBigInteger(nativeModPow(toByteArray(), exponent.toByteArray(), m.toByteArray()));
else
return super.modPow(exponent, m);
}
public byte[] toByteArray(){
if(cachedBa == null) //Since we are immutable it is safe to never update the cached ba after it has initially been generated
cachedBa = super.toByteArray();
return cachedBa;
}
return super.modPow(exponent, m);
public double doubleValue() {
if (_nativeOk)
return nativeDoubleValue(toByteArray());
else
return super.doubleValue();
}
/**
*
* @return True iff native methods will be used by this class
*/
public static boolean isNative(){
return _nativeOk;
}
/**
* <p>Compare the BigInteger.modPow vs the NativeBigInteger.modPow of some
* <p>Compare the BigInteger.modPow/doubleValue vs the NativeBigInteger.modPow/doubleValue of some
* really big (2Kbit) numbers 100 different times and benchmark the
* performance (or shit a brick if they don't match). </p>
*
*/
public static void main(String args[]) {
runTest(100);
runModPowTest(100);
runDoubleValueTest(100);
}
/* the sample numbers are elG generator/prime so we can test with reasonable numbers */
@@ -165,7 +252,7 @@ public class NativeBigInteger extends BigInteger {
+ "DE2BCBF6955817183995497CEA956AE515D2261898FA0510"
+ "15728E5A8AACAA68FFFFFFFFFFFFFFFF", 16).toByteArray();
private static void runTest(int numRuns) {
private static void runModPowTest(int numRuns) {
System.out.println("DEBUG: Warming up the random number generator...");
SecureRandom rand = new SecureRandom();
rand.nextBoolean();
@@ -197,10 +284,10 @@ public class NativeBigInteger extends BigInteger {
System.err.println("ERROR: java modPow value: " + jval.toString());
System.err.println("ERROR: run time: " + totalTime + "ms (" + (totalTime / (runsProcessed + 1)) + "ms each)");
break;
} else {
System.out.println("DEBUG: current run time: " + (afterModPow - beforeModPow) + "ms (total: "
+ totalTime + "ms, " + (totalTime / (runsProcessed + 1)) + "ms each)");
}
System.out.println("DEBUG: current run time: " + (afterModPow - beforeModPow) + "ms (total: "
+ totalTime + "ms, " + (totalTime / (runsProcessed + 1)) + "ms each)");
}
System.out.println("INFO: run time: " + totalTime + "ms (" + (totalTime / (runsProcessed + 1)) + "ms each)");
if (numRuns == runsProcessed)
@@ -212,44 +299,123 @@ public class NativeBigInteger extends BigInteger {
System.out.println("native run time: \t" + totalTime + "ms (" + (totalTime / (runsProcessed + 1))
+ "ms each)");
System.out.println("java run time: \t" + javaTime + "ms (" + (javaTime / (runsProcessed + 1)) + "ms each)");
System.out.println("native = " + ((totalTime * 100.0D) / javaTime) + "% of pure java time");
System.out.println("native = " + ((totalTime * 100.0d) / (double) javaTime) + "% of pure java time");
} else {
System.out.println("java run time: \t" + javaTime + "ms (" + (javaTime / (runsProcessed + 1)) + "ms each)");
System.out.println("However, we couldn't load the native library, so this doesn't test much");
}
}
private static void runDoubleValueTest(int numRuns) {
System.out.println("DEBUG: Warming up the random number generator...");
SecureRandom rand = new SecureRandom();
rand.nextBoolean();
System.out.println("DEBUG: Random number generator warmed up");
BigInteger jg = new BigInteger(_sampleGenerator);
long totalTime = 0;
long javaTime = 0;
int MULTIPLICATOR = 50000; //Run the doubleValue() calls within a loop since they are pretty fast..
int runsProcessed = 0;
for (runsProcessed = 0; runsProcessed < numRuns; runsProcessed++) {
NativeBigInteger g = new NativeBigInteger(_sampleGenerator);
long beforeDoubleValue = System.currentTimeMillis();
double dNative=0;
for(int mult=0;mult<MULTIPLICATOR;mult++)
dNative = g.doubleValue();
long afterDoubleValue = System.currentTimeMillis();
double jval=0;
for(int mult=0;mult<MULTIPLICATOR;mult++)
jval = jg.doubleValue();
long afterJavaDoubleValue = System.currentTimeMillis();
totalTime += (afterDoubleValue - beforeDoubleValue);
javaTime += (afterJavaDoubleValue - afterDoubleValue);
if (dNative!=jval) {
System.err.println("ERROR: [" + runsProcessed + "]\tnative double != java double");
System.err.println("ERROR: native double value: " + dNative);
System.err.println("ERROR: java double value: " + jval);
System.err.println("ERROR: run time: " + totalTime + "ms (" + (totalTime / (runsProcessed + 1)) + "ms each)");
break;
} else {
System.out.println("DEBUG: current run time: " + (afterDoubleValue - beforeDoubleValue) + "ms (total: "
+ totalTime + "ms, " + (totalTime / (runsProcessed + 1)) + "ms each)");
}
}
System.out.println("INFO: run time: " + totalTime + "ms (" + (totalTime / (runsProcessed + 1)) + "ms each)");
if (numRuns == runsProcessed)
System.out.println("INFO: " + runsProcessed + " runs complete without any errors");
else
System.out.println("ERROR: " + runsProcessed + " runs until we got an error");
if (_nativeOk) {
System.out.println("native run time: \t" + totalTime + "ms (" + (totalTime / (runsProcessed + 1))
+ "ms each)");
System.out.println("java run time: \t" + javaTime + "ms (" + (javaTime / (runsProcessed + 1)) + "ms each)");
System.out.println("native = " + ((totalTime * 100.0d) / (double) javaTime) + "% of pure java time");
} else {
System.out.println("java run time: \t" + javaTime + "ms (" + (javaTime / (runsProcessed + 1)) + "ms each)");
System.out.println("However, we couldn't load the native library, so this doesn't test much");
}
}
/**
* <p>Do whatever we can to load up the native library backing this BigInteger's modPow.
* <p>Do whatever we can to load up the native library backing this BigInteger's native methods.
* If it can find a custom built jbigi.dll / libjbigi.so, it'll use that. Otherwise
* it'll try to look in the classpath for the correct library (see loadFromResource).
* If the user specifies -Djbigi.enable=false it'll skip all of this.</p>
*
*/
private static final void loadNative() {
try{
String wantedProp = System.getProperty("jbigi.enable", "true");
boolean wantNative = "true".equalsIgnoreCase(wantedProp);
if (wantNative) {
boolean loaded = loadGeneric();
boolean loaded = loadFromResource("jbigi");
if (loaded) {
_nativeOk = true;
if (_doLog)
System.err.println("INFO: Native BigInteger library jbigi loaded");
System.err.println("INFO: Locally optimized native BigInteger loaded from resource");
} else {
loaded = loadFromResource();
loaded = loadFromResource(true);
if (loaded) {
_nativeOk = true;
if (_doLog)
System.err.println("INFO: Native BigInteger library jbigi loaded from resource");
System.err.println("INFO: Optimized native BigInteger library '"+getResourceName(true)+"' loaded from resource");
} else {
_nativeOk = false;
if (_doLog)
System.err.println("WARN: Native BigInteger library jbigi not loaded - using pure java");
loaded = loadGeneric(true);
if (loaded) {
_nativeOk = true;
if (_doLog)
System.err.println("INFO: Optimized native BigInteger library '"+getMiddleName(true)+"' loaded from somewhere in the path");
} else {
loaded = loadFromResource(false);
if (loaded) {
_nativeOk = true;
if (_doLog)
System.err.println("INFO: Non-optimized native BigInteger library '"+getResourceName(false)+"' loaded from resource");
} else {
loaded = loadGeneric(false);
if (loaded) {
_nativeOk = true;
if (_doLog)
System.err.println("INFO: Non-optimized native BigInteger library '"+getMiddleName(false)+"' loaded from somewhere in the path");
} else {
_nativeOk = false;
}
}
}
}
}
} else {
}
if (_doLog && !_nativeOk)
System.err.println("INFO: Native BigInteger library jbigi not loaded - using pure java");
}catch(Exception e){
if (_doLog)
System.err.println("INFO: Native BigInteger library jbigi not loaded - using pure java");
System.err.println("INFO: Native BigInteger library jbigi not loaded, reason: '"+e.getMessage()+"' - using pure java");
}
}
@@ -260,9 +426,12 @@ public class NativeBigInteger extends BigInteger {
* @return true if it was loaded successfully, else false
*
*/
private static final boolean loadGeneric() {
private static final boolean loadGeneric(boolean optimized) {
try {
System.loadLibrary("jbigi");
String name = getMiddleName(optimized);
if(name == null)
return false;
System.loadLibrary(name);
return true;
} catch (UnsatisfiedLinkError ule) {
return false;
@@ -286,13 +455,16 @@ public class NativeBigInteger extends BigInteger {
* @return true if it was loaded successfully, else false
*
*/
private static final boolean loadFromResource() {
String resourceName = getResourceName();
private static final boolean loadFromResource(boolean optimized) {
String resourceName = getResourceName(optimized);
return loadFromResource(resourceName);
}
private static final boolean loadFromResource(String resourceName) {
if (resourceName == null) return false;
URL resource = NativeBigInteger.class.getClassLoader().getResource(resourceName);
if (resource == null) {
if (_doLog)
System.err.println("ERROR: Resource name [" + resourceName + "] was not found");
System.err.println("NOTICE: Resource name [" + resourceName + "] was not found");
return false;
}
@@ -308,7 +480,7 @@ public class NativeBigInteger extends BigInteger {
fos.write(buf, 0, read);
}
fos.close();
System.load(outFile.getPath());
System.load(outFile.getAbsolutePath()); //System.load requires an absolute path to the lib
return true;
} catch (UnsatisfiedLinkError ule) {
if (_doLog) {
@@ -330,35 +502,53 @@ public class NativeBigInteger extends BigInteger {
}
}
private static final String getResourceName() {
String jbigiRefFile = System.getProperty("jbigi.ref");
if (jbigiRefFile == null)
jbigiRefFile = DEFAULT_REF_FILENAME;
private static final String getResourceName(boolean optimized) {
String pname = NativeBigInteger.class.getPackage().getName().replace('.','/');
String pref = getLibraryPrefix();
String middle = getMiddleName(optimized);
String suff = getLibrarySuffix();
if(pref == null || middle == null || suff == null)
return null;
return pname+"/"+pref+middle+"."+suff;
}
File refFile = new File(jbigiRefFile);
if (refFile.exists()) {
BufferedReader in = null;
try {
in = new BufferedReader(new InputStreamReader(new FileInputStream(refFile)));
String resourceName = in.readLine();
if ( (resourceName != null) || (resourceName.trim().length() <= 0) )
return resourceName;
} catch (IOException ioe) {
if (_doLog) {
System.err.println("WARN: Unable to read the jbigi reference file " + jbigiRefFile);
ioe.printStackTrace();
}
} finally {
if (in != null) {
try {
in.close();
} catch (IOException ioe) { // nop
}
}
}
}
private static final String getMiddleName(boolean optimized){
// the file doesnt exist, or it is empty, so use the jbigi.impl system env
return System.getProperty("jbigi.impl");
String sAppend;
if(optimized)
{
if(sCPUType == null)
return null;
else
sAppend = "-"+sCPUType;
}else
sAppend = "-none";
boolean isWindows =(System.getProperty("os.name").toLowerCase().indexOf("windows") != -1);
boolean isLinux =(System.getProperty("os.name").toLowerCase().indexOf("linux") != -1);
boolean isFreebsd =(System.getProperty("os.name").toLowerCase().indexOf("freebsd") != -1);
if(isWindows)
return "jbigi-windows"+sAppend; // The convention on Windows
if(isLinux)
return "jbigi-linux"+sAppend; // The convention on linux...
if(isFreebsd)
return "jbigi-freebsd"+sAppend; // The convention on freebsd...
throw new RuntimeException("Dont know jbigi library name for os type '"+System.getProperty("os.name")+"'");
}
private static final String getLibrarySuffix()
{
boolean isWindows =System.getProperty("os.name").toLowerCase().indexOf("windows") != -1;
if(isWindows)
return "dll";
else
return "so";
}
private static final String getLibraryPrefix()
{
boolean isWindows =System.getProperty("os.name").toLowerCase().indexOf("windows") != -1;
if(isWindows)
return "";
else
return "lib";
}
}