package net.i2p.crypto;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.math.BigInteger;
import java.util.concurrent.LinkedBlockingQueue;
import net.i2p.I2PAppContext;
import net.i2p.util.I2PThread;
import net.i2p.util.NativeBigInteger;
import net.i2p.util.SystemVersion;
/**
* Precalculate the Y and K for ElGamal encryption operations.
*
* This class precalcs a set of values on its own thread, using those transparently
* when a new instance is created. By default, the minimum threshold for creating
* new values for the pool is 20, and the max pool size is 50. Whenever the pool has
* less than the minimum, it fills it up again to the max. There is a delay after
* each precalculation so that the CPU isn't hosed during startup.
* These three parameters are controlled by java environmental variables and
* can be adjusted via:
* -Dcrypto.yk.precalc.min=40 -Dcrypto.yk.precalc.max=100 -Dcrypto.yk.precalc.delay=60000
*
* (delay is milliseconds)
*
* To disable precalculation, set min to 0
*
* @author jrandom
*/
final class YKGenerator {
//private final static Log _log = new Log(YKGenerator.class);
private final int MIN_NUM_BUILDERS;
private final int MAX_NUM_BUILDERS;
private final int CALC_DELAY;
private final LinkedBlockingQueue<BigInteger[]> _values;
private Thread _precalcThread;
private final I2PAppContext ctx;
private volatile boolean _isRunning;
public final static String PROP_YK_PRECALC_MIN = "crypto.yk.precalc.min";
public final static String PROP_YK_PRECALC_MAX = "crypto.yk.precalc.max";
public final static String PROP_YK_PRECALC_DELAY = "crypto.yk.precalc.delay";
public final static int DEFAULT_YK_PRECALC_MIN = 20;
public final static int DEFAULT_YK_PRECALC_MAX = 50;
public final static int DEFAULT_YK_PRECALC_DELAY = 200;
/**
* Caller must also call start() to start the background precalc thread.
* Unit tests will still work without calling start().
*/
public YKGenerator(I2PAppContext context) {
ctx = context;
// add to the defaults for every 128MB of RAM, up to 1GB
long maxMemory = SystemVersion.getMaxMemory();
int factor = (int) Math.max(1l, Math.min(8l, 1 + (maxMemory / (128*1024*1024l))));
int defaultMin = DEFAULT_YK_PRECALC_MIN * factor;
int defaultMax = DEFAULT_YK_PRECALC_MAX * factor;
MIN_NUM_BUILDERS = ctx.getProperty(PROP_YK_PRECALC_MIN, defaultMin);
MAX_NUM_BUILDERS = ctx.getProperty(PROP_YK_PRECALC_MAX, defaultMax);
CALC_DELAY = ctx.getProperty(PROP_YK_PRECALC_DELAY, DEFAULT_YK_PRECALC_DELAY);
_values = new LinkedBlockingQueue<BigInteger[]>(MAX_NUM_BUILDERS);
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("ElGamal YK Precalc (minimum: " + MIN_NUM_BUILDERS + " max: " + MAX_NUM_BUILDERS + ", delay: "
// + CALC_DELAY + ")");
ctx.statManager().createRateStat("crypto.YKUsed", "Need a YK from the queue", "Encryption", new long[] { 60*60*1000 });
ctx.statManager().createRateStat("crypto.YKEmpty", "YK queue empty", "Encryption", new long[] { 60*60*1000 });
}
/**
* Start the background precalc thread.
* Must be called for normal operation.
* If not called, all generation happens in the foreground.
* Not required for unit tests.
*
* @since 0.9.14
*/
public synchronized void start() {
if (_isRunning)
return;
_precalcThread = new I2PThread(new YKPrecalcRunner(MIN_NUM_BUILDERS, MAX_NUM_BUILDERS),
"YK Precalc", true);
_precalcThread.setPriority(Thread.NORM_PRIORITY - 2);
_isRunning = true;
_precalcThread.start();
}
/**
* Stop the background precalc thread.
* Can be restarted.
* Not required for unit tests.
*
* @since 0.8.8
*/
public synchronized void shutdown() {
_isRunning = false;
if (_precalcThread != null)
_precalcThread.interrupt();
_values.clear();
}
private final int getSize() {
return _values.size();
}
/** @return true if successful, false if full */
private final boolean addValues(BigInteger yk[]) {
return _values.offer(yk);
}
/** @return rv[0] = Y; rv[1] = K */
public BigInteger[] getNextYK() {
ctx.statManager().addRateData("crypto.YKUsed", 1);
BigInteger[] rv = _values.poll();
if (rv != null)
return rv;
ctx.statManager().addRateData("crypto.YKEmpty", 1);
return generateYK();
}
private final static BigInteger _two = new NativeBigInteger(1, new byte[] { 0x02});
/** @return rv[0] = Y; rv[1] = K */
private final BigInteger[] generateYK() {
NativeBigInteger k = null;
BigInteger y = null;
//long t0 = 0;
//long t1 = 0;
while (k == null) {
//t0 = Clock.getInstance().now();
k = new NativeBigInteger(ctx.keyGenerator().getElGamalExponentSize(), ctx.random());
//t1 = Clock.getInstance().now();
if (BigInteger.ZERO.compareTo(k) == 0) {
k = null;
continue;
}
BigInteger kPlus2 = k.add(_two);
if (kPlus2.compareTo(CryptoConstants.elgp) > 0) k = null;
}
//long t2 = Clock.getInstance().now();
y = CryptoConstants.elgg.modPow(k, CryptoConstants.elgp);
BigInteger yk[] = new BigInteger[2];
yk[0] = y;
yk[1] = k;
//long diff = t2 - t0;
//if (diff > 1000) {
// if (_log.shouldLog(Log.WARN)) _log.warn("Took too long to generate YK value for ElGamal (" + diff + "ms)");
//}
return yk;
}
/****
private static final int RUNS = 500;
public static void main(String args[]) {
// warmup crypto
ctx.random().nextInt();
System.out.println("Begin YK generator speed test");
long startNeg = Clock.getInstance().now();
for (int i = 0; i < RUNS; i++) {
getNextYK();
}
long endNeg = Clock.getInstance().now();
long negTime = endNeg - startNeg;
// 14 ms each on a 2008 netbook (with jbigi)
System.out.println("YK fetch time for " + RUNS + " runs: " + negTime + " @ " + (negTime / RUNS) + "ms each");
}
****/
/** the thread */
private class YKPrecalcRunner implements Runnable {
private final int _minSize;
private final int _maxSize;
/** check every 30 seconds whether we have less than the minimum */
private long _checkDelay = 30 * 1000;
private YKPrecalcRunner(int minSize, int maxSize) {
_minSize = minSize;
_maxSize = maxSize;
}
public void run() {
while (_isRunning) {
//long start = Clock.getInstance().now();
int startSize = getSize();
// Adjust delay
if (startSize <= (_minSize * 2 / 3) && _checkDelay > 1000)
_checkDelay -= 1000;
else if (startSize > (_minSize * 3 / 2) && _checkDelay < 60*1000)
_checkDelay += 1000;
if (startSize < _minSize) {
// fill all the way up, do the check here so we don't
// throw away one when full in addValues()
while (getSize() < _maxSize && _isRunning) {
//long begin = Clock.getInstance().now();
if (!addValues(generateYK()))
break;
//long end = Clock.getInstance().now();
//if (_log.shouldLog(Log.DEBUG)) _log.debug("Precalculated YK value in " + (end - begin) + "ms");
// for some relief...
try {
Thread.sleep(CALC_DELAY);
} catch (InterruptedException ie) { // nop
}
}
}
//long end = Clock.getInstance().now();
//int numCalc = curSize - startSize;
//if (numCalc > 0) {
// if (_log.shouldLog(Log.DEBUG))
// _log.debug("Precalced " + numCalc + " to " + curSize + " in "
// + (end - start - CALC_DELAY * numCalc) + "ms (not counting "
// + (CALC_DELAY * numCalc) + "ms relief). now sleeping");
//}
if (!_isRunning)
break;
try {
Thread.sleep(_checkDelay);
} catch (InterruptedException ie) { // nop
}
}
}
}
}