diff --git a/core/java/src/net/i2p/crypto/HMACSHA256Generator.java b/core/java/src/net/i2p/crypto/HMACSHA256Generator.java index 404532415..ae1399e22 100644 --- a/core/java/src/net/i2p/crypto/HMACSHA256Generator.java +++ b/core/java/src/net/i2p/crypto/HMACSHA256Generator.java @@ -9,12 +9,16 @@ import net.i2p.data.Hash; import net.i2p.data.SessionKey; import org.bouncycastle.crypto.digests.SHA256Digest; +import org.bouncycastle.crypto.digests.MD5Digest; import org.bouncycastle.crypto.macs.HMac; /** * Calculate the HMAC-SHA256 of a key+message. All the good stuff occurs * in {@link org.bouncycastle.crypto.macs.HMac} and - * {@link org.bouncycastle.crypto.digests.SHA256Digest}. + * {@link org.bouncycastle.crypto.digests.SHA256Digest}. Alternately, if + * the context property "i2p.HMACMD5" is set to true, then this whole HMAC + * generator will be transformed into HMACMD5, maintaining the same size and + * using {@link org.bouncycastle.crypto.digests.MD5Digest}. * */ public class HMACSHA256Generator { @@ -23,11 +27,18 @@ public class HMACSHA256Generator { private List _available; /** set of available byte[] buffers for verify */ private List _availableTmp; + private boolean _useMD5; + + public static final boolean DEFAULT_USE_MD5 = true; public HMACSHA256Generator(I2PAppContext context) { _context = context; _available = new ArrayList(32); _availableTmp = new ArrayList(32); + if ("true".equals(context.getProperty("i2p.HMACMD5", Boolean.toString(DEFAULT_USE_MD5).toLowerCase()))) + _useMD5 = true; + else + _useMD5 = false; } public static HMACSHA256Generator getInstance() { @@ -40,12 +51,15 @@ public class HMACSHA256Generator { public Hash calculate(SessionKey key, byte data[]) { if ((key == null) || (key.getData() == null) || (data == null)) throw new NullPointerException("Null arguments for HMAC"); - return calculate(key, data, 0, data.length); + byte rv[] = new byte[Hash.HASH_LENGTH]; + calculate(key, data, 0, data.length, rv, 0); + return new Hash(rv); } /** * Calculate the HMAC of the data with the given key */ + /* public Hash calculate(SessionKey key, byte data[], int offset, int length) { if ((key == null) || (key.getData() == null) || (data == null)) throw new NullPointerException("Null arguments for HMAC"); @@ -58,6 +72,23 @@ public class HMACSHA256Generator { release(mac); return new Hash(rv); } + */ + + /** + * Calculate the HMAC of the data with the given key + */ + public void calculate(SessionKey key, byte data[], int offset, int length, byte target[], int targetOffset) { + if ((key == null) || (key.getData() == null) || (data == null)) + throw new NullPointerException("Null arguments for HMAC"); + + HMac mac = acquire(); + mac.init(key.getData()); + mac.update(data, offset, length); + //byte rv[] = new byte[Hash.HASH_LENGTH]; + mac.doFinal(target, targetOffset); + release(mac); + //return new Hash(rv); + } /** * Verify the MAC inline, reducing some unnecessary memory churn. @@ -92,6 +123,9 @@ public class HMACSHA256Generator { if (_available.size() > 0) return (HMac)_available.remove(0); } + if (_useMD5) + return new HMac(new MD5Digest()); + else return new HMac(new SHA256Digest()); } private void release(HMac mac) { diff --git a/core/java/src/net/i2p/crypto/SHA256Generator.java b/core/java/src/net/i2p/crypto/SHA256Generator.java index 82ca186fc..22880a239 100644 --- a/core/java/src/net/i2p/crypto/SHA256Generator.java +++ b/core/java/src/net/i2p/crypto/SHA256Generator.java @@ -1,6 +1,8 @@ package net.i2p.crypto; import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; import net.i2p.I2PAppContext; import net.i2p.data.Hash; @@ -12,7 +14,10 @@ import org.bouncycastle.crypto.digests.SHA256Digest; * */ public final class SHA256Generator { - public SHA256Generator(I2PAppContext context) {} + private List _digests; + public SHA256Generator(I2PAppContext context) { + _digests = new ArrayList(32); + } public static final SHA256Generator getInstance() { return I2PAppContext.getGlobalContext().sha(); @@ -27,11 +32,35 @@ public final class SHA256Generator { } public final Hash calculateHash(byte[] source, int start, int len) { byte rv[] = new byte[Hash.HASH_LENGTH]; - SHA256Digest digest = new SHA256Digest(); - digest.update(source, start, len); - digest.doFinal(rv, 0); + calculateHash(source, start, len, rv, 0); return new Hash(rv); } + public final void calculateHash(byte[] source, int start, int len, byte out[], int outOffset) { + SHA256Digest digest = acquire(); + digest.update(source, start, len); + digest.doFinal(out, outOffset); + release(digest); + } + + private SHA256Digest acquire() { + SHA256Digest rv = null; + synchronized (_digests) { + if (_digests.size() > 0) + rv = (SHA256Digest)_digests.remove(0); + } + if (rv != null) + rv.reset(); + else + rv = new SHA256Digest(); + return rv; + } + private void release(SHA256Digest digest) { + synchronized (_digests) { + if (_digests.size() < 32) { + _digests.add(digest); + } + } + } public static void main(String args[]) { I2PAppContext ctx = I2PAppContext.getGlobalContext(); diff --git a/core/java/src/net/i2p/util/BufferedRandomSource.java b/core/java/src/net/i2p/util/BufferedRandomSource.java new file mode 100644 index 000000000..f78584057 --- /dev/null +++ b/core/java/src/net/i2p/util/BufferedRandomSource.java @@ -0,0 +1,222 @@ +package net.i2p.util; + +/* + * free (adj.): unencumbered; not under the control of others + * Written by jrandom in 2005 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.security.SecureRandom; + +import net.i2p.I2PAppContext; +import net.i2p.crypto.EntropyHarvester; +import net.i2p.data.Base64; +import net.i2p.data.DataHelper; + +/** + * Allocate data out of a large buffer of data, rather than the PRNG's + * (likely) small buffer to reduce the frequency of prng recalcs (though + * the recalcs are now more time consuming). + * + */ +public class BufferedRandomSource extends RandomSource { + private byte _buffer[]; + private int _nextByte; + private int _nextBit; + private static volatile long _reseeds; + + private static final int BUFFER_SIZE = 256*1024; + + public BufferedRandomSource(I2PAppContext context) { + super(context); + context.statManager().createRateStat("prng.reseedCount", "How many times the prng has been reseeded", "Encryption", new long[] { 60*1000, 10*60*1000, 60*60*1000 } ); + _buffer = new byte[BUFFER_SIZE]; + refillBuffer(); + // stagger reseeding + _nextByte = ((int)_reseeds-1) * 16 * 1024; + } + + private final void refillBuffer() { + long before = System.currentTimeMillis(); + doRefillBuffer(); + long duration = System.currentTimeMillis() - before; + if ( (_reseeds % 1) == 0) + _context.statManager().addRateData("prng.reseedCount", _reseeds, duration); + } + + private synchronized final void doRefillBuffer() { + super.nextBytes(_buffer); + _nextByte = 0; + _nextBit = 0; + _reseeds++; + } + + private static final byte GOBBLE_MASK[] = { 0x0, // 0 bits + 0x1, // 1 bit + 0x3, // 2 bits + 0x7, // 3 bits + 0xF, // 4 bits + 0x1F, // 5 bits + 0x3F, // 6 bits + 0x7F, // 7 bits + (byte)0xFF // 8 bits + }; + + private synchronized final long nextBits(int numBits) { + if (false) { + long rv = 0; + for (int curBit = 0; curBit < numBits; curBit++) { + if (_nextBit >= 8) { + _nextBit = 0; + _nextByte++; + } + if (_nextByte >= BUFFER_SIZE) + refillBuffer(); + rv += (_buffer[_nextByte] << curBit); + _nextBit++; + /* + int avail = 8 - _nextBit; + // this is not correct! (or is it?) + rv += (_buffer[_nextByte] << 8 - avail); + _nextBit += avail; + numBits -= avail; + if (_nextBit >= 8) { + _nextBit = 0; + _nextByte++; + } + */ + } + return rv; + } else { + long rv = 0; + int curBit = 0; + while (curBit < numBits) { + if (_nextBit >= 8) { + _nextBit = 0; + _nextByte++; + } + if (_nextByte >= BUFFER_SIZE) + refillBuffer(); + int gobbleBits = 8 - _nextBit; + int want = numBits - curBit; + if (gobbleBits > want) + gobbleBits = want; + curBit += gobbleBits; + int shift = 8 - _nextBit - gobbleBits; + int c = (_buffer[_nextByte] & (GOBBLE_MASK[gobbleBits] << shift)); + rv += ((c >>> shift) << (curBit-gobbleBits)); + _nextBit += gobbleBits; + } + return rv; + } + } + + public synchronized final void nextBytes(byte buf[]) { + int outOffset = 0; + while (outOffset < buf.length) { + int availableBytes = BUFFER_SIZE - _nextByte - (_nextBit != 0 ? 1 : 0); + if (availableBytes <= 0) + refillBuffer(); + int start = BUFFER_SIZE - availableBytes; + int writeSize = Math.min(buf.length - outOffset, availableBytes); + System.arraycopy(_buffer, start, buf, outOffset, writeSize); + outOffset += writeSize; + _nextByte += writeSize; + _nextBit = 0; + } + } + + public final int nextInt(int n) { + if (n <= 0) return 0; + int val = ((int)nextBits(countBits(n))) % n; + if (val < 0) + return 0 - val; + else + return val; + } + + public final int nextInt() { return nextInt(Integer.MAX_VALUE); } + + /** + * Like the modified nextInt, nextLong(n) returns a random number from 0 through n, + * including 0, excluding n. + */ + public final long nextLong(long n) { + if (n <= 0) return 0; + long val = nextBits(countBits(n)) % n; + if (val < 0) + return 0 - val; + else + return val; + } + + public final long nextLong() { return nextLong(Long.MAX_VALUE); } + + static final int countBits(long val) { + int rv = 0; + while (val > Integer.MAX_VALUE) { + rv += 31; + val >>>= 31; + } + + while (val > 0) { + rv++; + val >>= 1; + } + return rv; + } + + /** + * override as synchronized, for those JVMs that don't always pull via + * nextBytes (cough ibm) + */ + public final boolean nextBoolean() { + return nextBits(1) != 0; + } + + private static final double DOUBLE_DENOMENATOR = (double)(1L << 53); + /** defined per javadoc ( ((nextBits(26)<<27) + nextBits(27)) / (1 << 53)) */ + public final double nextDouble() { + long top = (((long)nextBits(26) << 27) + nextBits(27)); + return top / DOUBLE_DENOMENATOR; + } + private static final float FLOAT_DENOMENATOR = (float)(1 << 24); + /** defined per javadoc (nextBits(24) / ((float)(1 << 24)) ) */ + public float nextFloat() { + long top = nextBits(24); + return top / FLOAT_DENOMENATOR; + } + public double nextGaussian() { + // bah, unbuffered + return super.nextGaussian(); + } + + public static void main(String args[]) { + I2PAppContext ctx = I2PAppContext.getGlobalContext(); + byte data[] = new byte[16*1024]; + for (int i = 0; i < data.length; i += 4) { + long l = ctx.random().nextLong(); + if (l < 0) l = 0 - l; + DataHelper.toLong(data, i, 4, l); + } + byte compressed[] = DataHelper.compress(data); + System.out.println("Compressed: " + compressed.length); + System.out.println("Orig: " + data.length + ": " + toString(data)); + } + private static final String toString(byte data[]) { + StringBuffer buf = new StringBuffer(data.length * 9); + for (int i = 0; i < data.length; i++) { + for (int j = 0; j < 8; j++) { + if ((data[i] & (1 << j)) != 0) + buf.append('1'); + else + buf.append('0'); + } + buf.append(' '); + } + return buf.toString(); + } +} \ No newline at end of file diff --git a/core/java/src/net/i2p/util/PooledRandomSource.java b/core/java/src/net/i2p/util/PooledRandomSource.java index 1b4cab832..d7dc0176d 100644 --- a/core/java/src/net/i2p/util/PooledRandomSource.java +++ b/core/java/src/net/i2p/util/PooledRandomSource.java @@ -27,7 +27,8 @@ public class PooledRandomSource extends RandomSource { _log = context.logManager().getLog(PooledRandomSource.class); _pool = new RandomSource[POOL_SIZE]; for (int i = 0; i < POOL_SIZE; i++) { - _pool[i] = new RandomSource(context); + //_pool[i] = new RandomSource(context); + _pool[i] = new BufferedRandomSource(context); _pool[i].nextBoolean(); } _nextPool = 0; diff --git a/core/java/src/net/i2p/util/RandomSource.java b/core/java/src/net/i2p/util/RandomSource.java index 4e22e8dce..bb26828c4 100644 --- a/core/java/src/net/i2p/util/RandomSource.java +++ b/core/java/src/net/i2p/util/RandomSource.java @@ -22,9 +22,11 @@ import net.i2p.crypto.EntropyHarvester; public class RandomSource extends SecureRandom { private Log _log; private EntropyHarvester _entropyHarvester; + protected I2PAppContext _context; public RandomSource(I2PAppContext context) { super(); + _context = context; _log = context.logManager().getLog(RandomSource.class); // when we replace to have hooks for fortuna (etc), replace with // a factory (or just a factory method) diff --git a/core/java/src/org/bouncycastle/crypto/digests/MD5Digest.java b/core/java/src/org/bouncycastle/crypto/digests/MD5Digest.java new file mode 100644 index 000000000..cd434b911 --- /dev/null +++ b/core/java/src/org/bouncycastle/crypto/digests/MD5Digest.java @@ -0,0 +1,302 @@ +package org.bouncycastle.crypto.digests; + + +/** + * implementation of MD5 as outlined in "Handbook of Applied Cryptography", pages 346 - 347. + */ +public class MD5Digest + extends GeneralDigest +{ + private static final int DIGEST_LENGTH = 16; + + private int H1, H2, H3, H4; // IV's + + private int[] X = new int[16]; + private int xOff; + + /** + * Standard constructor + */ + public MD5Digest() + { + reset(); + } + + /** + * Copy constructor. This will copy the state of the provided + * message digest. + */ + public MD5Digest(MD5Digest t) + { + super(t); + + H1 = t.H1; + H2 = t.H2; + H3 = t.H3; + H4 = t.H4; + + System.arraycopy(t.X, 0, X, 0, t.X.length); + xOff = t.xOff; + } + + public String getAlgorithmName() + { + return "MD5"; + } + + public int getDigestSize() + { + return DIGEST_LENGTH; + } + + protected void processWord( + byte[] in, + int inOff) + { + X[xOff++] = (in[inOff] & 0xff) | ((in[inOff + 1] & 0xff) << 8) + | ((in[inOff + 2] & 0xff) << 16) | ((in[inOff + 3] & 0xff) << 24); + + if (xOff == 16) + { + processBlock(); + } + } + + protected void processLength( + long bitLength) + { + if (xOff > 14) + { + processBlock(); + } + + X[14] = (int)(bitLength & 0xffffffff); + X[15] = (int)(bitLength >>> 32); + } + + private void unpackWord( + int word, + byte[] out, + int outOff) + { + out[outOff] = (byte)word; + out[outOff + 1] = (byte)(word >>> 8); + out[outOff + 2] = (byte)(word >>> 16); + out[outOff + 3] = (byte)(word >>> 24); + } + + public int doFinal( + byte[] out, + int outOff) + { + finish(); + + unpackWord(H1, out, outOff); + unpackWord(H2, out, outOff + 4); + unpackWord(H3, out, outOff + 8); + unpackWord(H4, out, outOff + 12); + + reset(); + + return DIGEST_LENGTH; + } + + /** + * reset the chaining variables to the IV values. + */ + public void reset() + { + super.reset(); + + H1 = 0x67452301; + H2 = 0xefcdab89; + H3 = 0x98badcfe; + H4 = 0x10325476; + + xOff = 0; + + for (int i = 0; i != X.length; i++) + { + X[i] = 0; + } + } + + // + // round 1 left rotates + // + private static final int S11 = 7; + private static final int S12 = 12; + private static final int S13 = 17; + private static final int S14 = 22; + + // + // round 2 left rotates + // + private static final int S21 = 5; + private static final int S22 = 9; + private static final int S23 = 14; + private static final int S24 = 20; + + // + // round 3 left rotates + // + private static final int S31 = 4; + private static final int S32 = 11; + private static final int S33 = 16; + private static final int S34 = 23; + + // + // round 4 left rotates + // + private static final int S41 = 6; + private static final int S42 = 10; + private static final int S43 = 15; + private static final int S44 = 21; + + /* + * rotate int x left n bits. + */ + private int rotateLeft( + int x, + int n) + { + return (x << n) | (x >>> (32 - n)); + } + + /* + * F, G, H and I are the basic MD5 functions. + */ + private int F( + int u, + int v, + int w) + { + return (u & v) | (~u & w); + } + + private int G( + int u, + int v, + int w) + { + return (u & w) | (v & ~w); + } + + private int H( + int u, + int v, + int w) + { + return u ^ v ^ w; + } + + private int K( + int u, + int v, + int w) + { + return v ^ (u | ~w); + } + + protected void processBlock() + { + int a = H1; + int b = H2; + int c = H3; + int d = H4; + + // + // Round 1 - F cycle, 16 times. + // + a = rotateLeft((a + F(b, c, d) + X[ 0] + 0xd76aa478), S11) + b; + d = rotateLeft((d + F(a, b, c) + X[ 1] + 0xe8c7b756), S12) + a; + c = rotateLeft((c + F(d, a, b) + X[ 2] + 0x242070db), S13) + d; + b = rotateLeft((b + F(c, d, a) + X[ 3] + 0xc1bdceee), S14) + c; + a = rotateLeft((a + F(b, c, d) + X[ 4] + 0xf57c0faf), S11) + b; + d = rotateLeft((d + F(a, b, c) + X[ 5] + 0x4787c62a), S12) + a; + c = rotateLeft((c + F(d, a, b) + X[ 6] + 0xa8304613), S13) + d; + b = rotateLeft((b + F(c, d, a) + X[ 7] + 0xfd469501), S14) + c; + a = rotateLeft((a + F(b, c, d) + X[ 8] + 0x698098d8), S11) + b; + d = rotateLeft((d + F(a, b, c) + X[ 9] + 0x8b44f7af), S12) + a; + c = rotateLeft((c + F(d, a, b) + X[10] + 0xffff5bb1), S13) + d; + b = rotateLeft((b + F(c, d, a) + X[11] + 0x895cd7be), S14) + c; + a = rotateLeft((a + F(b, c, d) + X[12] + 0x6b901122), S11) + b; + d = rotateLeft((d + F(a, b, c) + X[13] + 0xfd987193), S12) + a; + c = rotateLeft((c + F(d, a, b) + X[14] + 0xa679438e), S13) + d; + b = rotateLeft((b + F(c, d, a) + X[15] + 0x49b40821), S14) + c; + + // + // Round 2 - G cycle, 16 times. + // + a = rotateLeft((a + G(b, c, d) + X[ 1] + 0xf61e2562), S21) + b; + d = rotateLeft((d + G(a, b, c) + X[ 6] + 0xc040b340), S22) + a; + c = rotateLeft((c + G(d, a, b) + X[11] + 0x265e5a51), S23) + d; + b = rotateLeft((b + G(c, d, a) + X[ 0] + 0xe9b6c7aa), S24) + c; + a = rotateLeft((a + G(b, c, d) + X[ 5] + 0xd62f105d), S21) + b; + d = rotateLeft((d + G(a, b, c) + X[10] + 0x02441453), S22) + a; + c = rotateLeft((c + G(d, a, b) + X[15] + 0xd8a1e681), S23) + d; + b = rotateLeft((b + G(c, d, a) + X[ 4] + 0xe7d3fbc8), S24) + c; + a = rotateLeft((a + G(b, c, d) + X[ 9] + 0x21e1cde6), S21) + b; + d = rotateLeft((d + G(a, b, c) + X[14] + 0xc33707d6), S22) + a; + c = rotateLeft((c + G(d, a, b) + X[ 3] + 0xf4d50d87), S23) + d; + b = rotateLeft((b + G(c, d, a) + X[ 8] + 0x455a14ed), S24) + c; + a = rotateLeft((a + G(b, c, d) + X[13] + 0xa9e3e905), S21) + b; + d = rotateLeft((d + G(a, b, c) + X[ 2] + 0xfcefa3f8), S22) + a; + c = rotateLeft((c + G(d, a, b) + X[ 7] + 0x676f02d9), S23) + d; + b = rotateLeft((b + G(c, d, a) + X[12] + 0x8d2a4c8a), S24) + c; + + // + // Round 3 - H cycle, 16 times. + // + a = rotateLeft((a + H(b, c, d) + X[ 5] + 0xfffa3942), S31) + b; + d = rotateLeft((d + H(a, b, c) + X[ 8] + 0x8771f681), S32) + a; + c = rotateLeft((c + H(d, a, b) + X[11] + 0x6d9d6122), S33) + d; + b = rotateLeft((b + H(c, d, a) + X[14] + 0xfde5380c), S34) + c; + a = rotateLeft((a + H(b, c, d) + X[ 1] + 0xa4beea44), S31) + b; + d = rotateLeft((d + H(a, b, c) + X[ 4] + 0x4bdecfa9), S32) + a; + c = rotateLeft((c + H(d, a, b) + X[ 7] + 0xf6bb4b60), S33) + d; + b = rotateLeft((b + H(c, d, a) + X[10] + 0xbebfbc70), S34) + c; + a = rotateLeft((a + H(b, c, d) + X[13] + 0x289b7ec6), S31) + b; + d = rotateLeft((d + H(a, b, c) + X[ 0] + 0xeaa127fa), S32) + a; + c = rotateLeft((c + H(d, a, b) + X[ 3] + 0xd4ef3085), S33) + d; + b = rotateLeft((b + H(c, d, a) + X[ 6] + 0x04881d05), S34) + c; + a = rotateLeft((a + H(b, c, d) + X[ 9] + 0xd9d4d039), S31) + b; + d = rotateLeft((d + H(a, b, c) + X[12] + 0xe6db99e5), S32) + a; + c = rotateLeft((c + H(d, a, b) + X[15] + 0x1fa27cf8), S33) + d; + b = rotateLeft((b + H(c, d, a) + X[ 2] + 0xc4ac5665), S34) + c; + + // + // Round 4 - K cycle, 16 times. + // + a = rotateLeft((a + K(b, c, d) + X[ 0] + 0xf4292244), S41) + b; + d = rotateLeft((d + K(a, b, c) + X[ 7] + 0x432aff97), S42) + a; + c = rotateLeft((c + K(d, a, b) + X[14] + 0xab9423a7), S43) + d; + b = rotateLeft((b + K(c, d, a) + X[ 5] + 0xfc93a039), S44) + c; + a = rotateLeft((a + K(b, c, d) + X[12] + 0x655b59c3), S41) + b; + d = rotateLeft((d + K(a, b, c) + X[ 3] + 0x8f0ccc92), S42) + a; + c = rotateLeft((c + K(d, a, b) + X[10] + 0xffeff47d), S43) + d; + b = rotateLeft((b + K(c, d, a) + X[ 1] + 0x85845dd1), S44) + c; + a = rotateLeft((a + K(b, c, d) + X[ 8] + 0x6fa87e4f), S41) + b; + d = rotateLeft((d + K(a, b, c) + X[15] + 0xfe2ce6e0), S42) + a; + c = rotateLeft((c + K(d, a, b) + X[ 6] + 0xa3014314), S43) + d; + b = rotateLeft((b + K(c, d, a) + X[13] + 0x4e0811a1), S44) + c; + a = rotateLeft((a + K(b, c, d) + X[ 4] + 0xf7537e82), S41) + b; + d = rotateLeft((d + K(a, b, c) + X[11] + 0xbd3af235), S42) + a; + c = rotateLeft((c + K(d, a, b) + X[ 2] + 0x2ad7d2bb), S43) + d; + b = rotateLeft((b + K(c, d, a) + X[ 9] + 0xeb86d391), S44) + c; + + H1 += a; + H2 += b; + H3 += c; + H4 += d; + + // + // reset the offset and clean out the word buffer. + // + xOff = 0; + for (int i = 0; i != X.length; i++) + { + X[i] = 0; + } + } +} diff --git a/core/java/test/net/i2p/crypto/HMACSHA256Bench.java b/core/java/test/net/i2p/crypto/HMACSHA256Bench.java index 44c9aed23..fe03719bc 100644 --- a/core/java/test/net/i2p/crypto/HMACSHA256Bench.java +++ b/core/java/test/net/i2p/crypto/HMACSHA256Bench.java @@ -37,10 +37,11 @@ import java.util.Properties; public class HMACSHA256Bench { public static void main(String args[]) { runTest(new I2PAppContext()); + System.out.println("Running as MD5"); Properties props = new Properties(); - props.setProperty("i2p.fakeHMAC", "true"); + //props.setProperty("i2p.fakeHMAC", "true"); + props.setProperty("i2p.HMACMD5", "true"); runTest(new I2PAppContext(props)); - } private static void runTest(I2PAppContext ctx) { SessionKey key = ctx.keyGenerator().generateSessionKey(); @@ -109,7 +110,7 @@ public class HMACSHA256Bench { private static void display(int times, long before, long after, int len, String name) { double rate = ((double)times)/(((double)after-(double)before)/1000.0d); double kbps = ((double)len/1024.0d)*((double)times)/(((double)after-(double)before)/1000.0d); - System.out.println(name + " HMAC-SHA256 pulled " + kbps + "KBps or " + rate + " calcs per second"); + System.out.println(name + " HMAC pulled " + kbps + "KBps or " + rate + " calcs per second"); } } diff --git a/history.txt b/history.txt index 0b6d9ebb0..d6bf86e0c 100644 --- a/history.txt +++ b/history.txt @@ -1,4 +1,10 @@ -$Id: history.txt,v 1.206 2005/05/25 16:32:38 duck Exp $ +$Id: history.txt,v 1.207 2005/07/04 15:44:17 jrandom Exp $ + +2005-07-05 + * Use a buffered PRNG, pulling the PRNG data off a larger precalculated + buffer, rather than the underlying PRNG's (likely small) one, which in + turn reduces the frequency of recalcing. + * More tuning to reduce temporary allocation churn 2005-07-04 jrandom * Within the tunnel, use xor(IV, msg[0:16]) as the flag to detect dups, diff --git a/router/java/src/net/i2p/data/i2np/TunnelDataMessage.java b/router/java/src/net/i2p/data/i2np/TunnelDataMessage.java index b326866b3..69555eaa4 100644 --- a/router/java/src/net/i2p/data/i2np/TunnelDataMessage.java +++ b/router/java/src/net/i2p/data/i2np/TunnelDataMessage.java @@ -22,7 +22,8 @@ import net.i2p.util.Log; */ public class TunnelDataMessage extends I2NPMessageImpl { private Log _log; - private TunnelId _tunnelId; + private long _tunnelId; + private TunnelId _tunnelIdObj; private byte[] _data; public final static int MESSAGE_TYPE = 18; @@ -48,8 +49,17 @@ public class TunnelDataMessage extends I2NPMessageImpl { setMessageExpiration(context.clock().now() + EXPIRATION_PERIOD); } - public TunnelId getTunnelId() { return _tunnelId; } - public void setTunnelId(TunnelId id) { _tunnelId = id; } + public long getTunnelId() { return _tunnelId; } + public void setTunnelId(long id) { _tunnelId = id; } + public TunnelId getTunnelIdObj() { + if (_tunnelIdObj == null) + _tunnelIdObj = new TunnelId(_tunnelId); // not thread safe, but immutable, so who cares + return _tunnelIdObj; + } + public void setTunnelId(TunnelId id) { + _tunnelIdObj = id; + _tunnelId = id.getTunnelId(); + } public byte[] getData() { return _data; } public void setData(byte data[]) { @@ -62,10 +72,10 @@ public class TunnelDataMessage extends I2NPMessageImpl { if (type != MESSAGE_TYPE) throw new I2NPMessageException("Message type is incorrect for this message"); int curIndex = offset; - _tunnelId = new TunnelId(DataHelper.fromLong(data, curIndex, 4)); + _tunnelId = DataHelper.fromLong(data, curIndex, 4); curIndex += 4; - if (_tunnelId.getTunnelId() <= 0) + if (_tunnelId <= 0) throw new I2NPMessageException("Invalid tunnel Id " + _tunnelId); // we cant cache it in trivial form, as other components (e.g. HopProcessor) @@ -82,12 +92,12 @@ public class TunnelDataMessage extends I2NPMessageImpl { protected int calculateWrittenLength() { return 4 + DATA_SIZE; } /** write the message body to the output array, starting at the given index */ protected int writeMessageBody(byte out[], int curIndex) throws I2NPMessageException { - if ( (_tunnelId == null) || (_data == null) ) + if ( (_tunnelId <= 0) || (_data == null) ) throw new I2NPMessageException("Not enough data to write out (id=" + _tunnelId + " data=" + _data + ")"); if (_data.length <= 0) throw new I2NPMessageException("Not enough data to write out (data.length=" + _data.length + ")"); - DataHelper.toLong(out, curIndex, 4, _tunnelId.getTunnelId()); + DataHelper.toLong(out, curIndex, 4, _tunnelId); curIndex += 4; System.arraycopy(_data, 0, out, curIndex, DATA_SIZE); curIndex += _data.length; @@ -99,7 +109,7 @@ public class TunnelDataMessage extends I2NPMessageImpl { public int getType() { return MESSAGE_TYPE; } public int hashCode() { - return DataHelper.hashCode(getTunnelId()) + + return (int)_tunnelId + DataHelper.hashCode(_data); } diff --git a/router/java/src/net/i2p/router/MessageHistory.java b/router/java/src/net/i2p/router/MessageHistory.java index 8d1476ab2..b401f05c9 100644 --- a/router/java/src/net/i2p/router/MessageHistory.java +++ b/router/java/src/net/i2p/router/MessageHistory.java @@ -219,6 +219,11 @@ public class MessageHistory { addEntry(getPrefix() + "message " + messageId + " on tunnel " + tunnelId + " / " + toTunnel + " as " + type); } + public void tunnelDispatched(long messageId, long innerMessageId, long tunnelId, String type) { + if (!_doLog) return; + addEntry(getPrefix() + "message " + messageId + "/" + innerMessageId + " on " + tunnelId + " as " + type); + } + /** * The local router has detected a failure in the given tunnel * @@ -472,13 +477,14 @@ public class MessageHistory { buf.append("Break message ").append(messageId).append(" into fragments: ").append(numFragments); addEntry(buf.toString()); } - public void fragmentMessage(long messageId, int numFragments, String tunnel) { + public void fragmentMessage(long messageId, int numFragments, Object tunnel) { if (!_doLog) return; if (messageId == -1) throw new IllegalArgumentException("why are you -1?"); StringBuffer buf = new StringBuffer(48); buf.append(getPrefix()); buf.append("Break message ").append(messageId).append(" into fragments: ").append(numFragments); - buf.append(" on ").append(tunnel); + if (tunnel != null) + buf.append(" on ").append(tunnel.toString()); addEntry(buf.toString()); } public void droppedTunnelDataMessageUnknown(long msgId, long tunnelId) { diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index b391a54ca..21dea88cb 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -15,9 +15,9 @@ import net.i2p.CoreVersion; * */ public class RouterVersion { - public final static String ID = "$Revision: 1.197 $ $Date: 2005/05/25 16:32:38 $"; + public final static String ID = "$Revision: 1.198 $ $Date: 2005/07/04 15:44:21 $"; public final static String VERSION = "0.5.0.7"; - public final static long BUILD = 9; + public final static long BUILD = 10; public static void main(String args[]) { System.out.println("I2P Router version: " + VERSION); System.out.println("Router ID: " + RouterVersion.ID); diff --git a/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java b/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java index f9ff4a89c..b371c0b02 100644 --- a/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java +++ b/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java @@ -61,6 +61,9 @@ public class CommSystemFacadeImpl extends CommSystemFacade { public TransportBid getBid(OutNetMessage msg) { return _manager.getBid(msg); } + public TransportBid getNextBid(OutNetMessage msg) { + return _manager.getNextBid(msg); + } public void processMessage(OutNetMessage msg) { //GetBidsJob j = new GetBidsJob(_context, this, msg); diff --git a/router/java/src/net/i2p/router/transport/GetBidsJob.java b/router/java/src/net/i2p/router/transport/GetBidsJob.java index 659099f53..4d5c5e630 100644 --- a/router/java/src/net/i2p/router/transport/GetBidsJob.java +++ b/router/java/src/net/i2p/router/transport/GetBidsJob.java @@ -59,28 +59,13 @@ public class GetBidsJob extends JobImpl { return; } - List bids = facade.getBids(msg); - - if ( (bids == null) || (bids.size() <= 0) ) { - context.shitlist().shitlistRouter(to, "No bids after " + (bids != null ? bids.size() + " tries" : "0 tries")); + TransportBid bid = facade.getNextBid(msg); + if (bid == null) { + context.shitlist().shitlistRouter(to, "No more bids available"); context.netDb().fail(to); fail(context, msg); } else { - int lowestCost = -1; - TransportBid winner = null; - for (int i = 0; i < bids.size(); i++) { - TransportBid bid = (TransportBid)bids.get(i); - if ( (lowestCost < 0) || (bid.getLatencyMs() < lowestCost) ) { - winner = bid; - lowestCost = bid.getLatencyMs(); - } - } - if (winner != null) { - if (log.shouldLog(Log.INFO)) - log.info("Winning bid: " + winner + " out of " + bids); - - winner.getTransport().send(msg); - } + bid.getTransport().send(msg); } } diff --git a/router/java/src/net/i2p/router/transport/TransportManager.java b/router/java/src/net/i2p/router/transport/TransportManager.java index aaebcedbb..d8b3ea3ce 100644 --- a/router/java/src/net/i2p/router/transport/TransportManager.java +++ b/router/java/src/net/i2p/router/transport/TransportManager.java @@ -159,6 +159,33 @@ public class TransportManager implements TransportEventListener { return rv; } + public TransportBid getNextBid(OutNetMessage msg) { + Set failedTransports = msg.getFailedTransports(); + TransportBid rv = null; + for (int i = 0; i < _transports.size(); i++) { + Transport t = (Transport)_transports.get(i); + if (failedTransports.contains(t.getStyle())) { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Skipping transport " + t.getStyle() + " as it already failed"); + continue; + } + // we always want to try all transports, in case there is a faster bidirectional one + // already connected (e.g. peer only has a public PHTTP address, but they've connected + // to us via TCP, send via TCP) + TransportBid bid = t.bid(msg.getTarget(), msg.getMessageSize()); + if (bid != null) { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Transport " + t.getStyle() + " bid: " + bid); + if ( (rv == null) || (rv.getLatencyMs() > bid.getLatencyMs()) ) + rv = bid; + } else { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Transport " + t.getStyle() + " did not produce a bid"); + } + } + return rv; + } + public void messageReceived(I2NPMessage message, RouterIdentity fromRouter, Hash fromRouterHash) { if (_log.shouldLog(Log.DEBUG)) _log.debug("I2NPMessage received: " + message.getClass().getName(), new Exception("Where did I come from again?")); diff --git a/router/java/src/net/i2p/router/transport/udp/ACKSender.java b/router/java/src/net/i2p/router/transport/udp/ACKSender.java index 173e8156b..986a9bd45 100644 --- a/router/java/src/net/i2p/router/transport/udp/ACKSender.java +++ b/router/java/src/net/i2p/router/transport/udp/ACKSender.java @@ -101,7 +101,7 @@ public class ACKSender implements Runnable { _context.statManager().addRateData("udp.sendACKRemaining", remaining, 0); now = _context.clock().now(); _context.statManager().addRateData("udp.ackFrequency", now-lastSend, now-wanted); - _context.statManager().getStatLog().addData(peer.getRemoteHostId().toString(), "udp.peer.sendACKCount", ackBitfields.size(), 0); + //_context.statManager().getStatLog().addData(peer.getRemoteHostId().toString(), "udp.peer.sendACKCount", ackBitfields.size(), 0); UDPPacket ack = _builder.buildACK(peer, ackBitfields); ack.markType(1); if (_log.shouldLog(Log.INFO)) diff --git a/router/java/src/net/i2p/router/transport/udp/OutboundMessageState.java b/router/java/src/net/i2p/router/transport/udp/OutboundMessageState.java index fdf715ace..b5aa7eae8 100644 --- a/router/java/src/net/i2p/router/transport/udp/OutboundMessageState.java +++ b/router/java/src/net/i2p/router/transport/udp/OutboundMessageState.java @@ -34,7 +34,7 @@ public class OutboundMessageState { private int _nextSendFragment; public static final int MAX_FRAGMENTS = 32; - private static final ByteCache _cache = ByteCache.getInstance(64, MAX_FRAGMENTS*1024); + private static final ByteCache _cache = ByteCache.getInstance(128, MAX_FRAGMENTS*1024); public OutboundMessageState(I2PAppContext context) { _context = context; diff --git a/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java b/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java index 21b5cf811..0e84c1d8a 100644 --- a/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java +++ b/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java @@ -27,6 +27,7 @@ public class PacketBuilder { private Log _log; private static final ByteCache _ivCache = ByteCache.getInstance(64, UDPPacket.IV_SIZE); + private static final ByteCache _hmacCache = ByteCache.getInstance(64, Hash.HASH_LENGTH); public PacketBuilder(I2PAppContext ctx) { _context = ctx; @@ -482,16 +483,20 @@ public class PacketBuilder { int hmacOff = packet.getPacket().getOffset(); int hmacLen = encryptSize + UDPPacket.IV_SIZE + 2; - Hash hmac = _context.hmac().calculate(macKey, data, hmacOff, hmacLen); + //Hash hmac = _context.hmac().calculate(macKey, data, hmacOff, hmacLen); + ByteArray ba = _hmacCache.acquire(); + _context.hmac().calculate(macKey, data, hmacOff, hmacLen, ba.getData(), 0); if (_log.shouldLog(Log.DEBUG)) _log.debug("Authenticating " + packet.getPacketDataLength() + // packet.getPacket().getLength() + "\nIV: " + Base64.encode(iv.getData()) + - "\nraw mac: " + hmac.toBase64() + + "\nraw mac: " + Base64.encode(ba.getData()) + "\nMAC key: " + macKey.toBase64()); // ok, now lets put it back where it belongs... System.arraycopy(data, hmacOff, data, encryptOffset, encryptSize); - System.arraycopy(hmac.getData(), 0, data, hmacOff, UDPPacket.MAC_SIZE); + //System.arraycopy(hmac.getData(), 0, data, hmacOff, UDPPacket.MAC_SIZE); + System.arraycopy(ba.getData(), 0, data, hmacOff, UDPPacket.MAC_SIZE); System.arraycopy(iv.getData(), 0, data, hmacOff + UDPPacket.MAC_SIZE, UDPPacket.IV_SIZE); + _hmacCache.release(ba); } } diff --git a/router/java/src/net/i2p/router/transport/udp/UDPPacket.java b/router/java/src/net/i2p/router/transport/udp/UDPPacket.java index 3da40ae97..cfcc6d7ed 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPPacket.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPPacket.java @@ -68,9 +68,9 @@ public class UDPPacket { public static final byte BITFIELD_CONTINUATION = (byte)(1 << 7); private static final int MAX_VALIDATE_SIZE = MAX_PACKET_SIZE; - private static final ByteCache _validateCache = ByteCache.getInstance(16, MAX_VALIDATE_SIZE); - private static final ByteCache _ivCache = ByteCache.getInstance(16, IV_SIZE); - private static final ByteCache _dataCache = ByteCache.getInstance(64, MAX_PACKET_SIZE); + private static final ByteCache _validateCache = ByteCache.getInstance(64, MAX_VALIDATE_SIZE); + private static final ByteCache _ivCache = ByteCache.getInstance(64, IV_SIZE); + private static final ByteCache _dataCache = ByteCache.getInstance(128, MAX_PACKET_SIZE); private UDPPacket(I2PAppContext ctx) { _context = ctx; @@ -237,8 +237,11 @@ public class UDPPacket { _released = true; //_releasedBy = new Exception("released by"); //_acquiredBy = null; - //_dataCache.release(_dataBuf); - if (!CACHE) return; + // + if (!CACHE) { + _dataCache.release(_dataBuf); + return; + } synchronized (_packetCache) { if (_packetCache.size() <= 64) { _packetCache.add(this); diff --git a/router/java/src/net/i2p/router/tunnel/BatchedRouterPreprocessor.java b/router/java/src/net/i2p/router/tunnel/BatchedRouterPreprocessor.java index 01e42bfb1..ca980d842 100644 --- a/router/java/src/net/i2p/router/tunnel/BatchedRouterPreprocessor.java +++ b/router/java/src/net/i2p/router/tunnel/BatchedRouterPreprocessor.java @@ -57,8 +57,8 @@ public class BatchedRouterPreprocessor extends BatchedPreprocessor { protected void notePreprocessing(long messageId, int numFragments) { if (_config != null) - _routerContext.messageHistory().fragmentMessage(messageId, numFragments, _config.toString()); + _routerContext.messageHistory().fragmentMessage(messageId, numFragments, _config); else - _routerContext.messageHistory().fragmentMessage(messageId, numFragments, _hopConfig.toString()); + _routerContext.messageHistory().fragmentMessage(messageId, numFragments, _hopConfig); } } diff --git a/router/java/src/net/i2p/router/tunnel/TrivialPreprocessor.java b/router/java/src/net/i2p/router/tunnel/TrivialPreprocessor.java index 6361d6e25..d73e41270 100644 --- a/router/java/src/net/i2p/router/tunnel/TrivialPreprocessor.java +++ b/router/java/src/net/i2p/router/tunnel/TrivialPreprocessor.java @@ -26,6 +26,7 @@ public class TrivialPreprocessor implements TunnelGateway.QueuePreprocessor { protected static final int IV_SIZE = HopProcessor.IV_LENGTH; protected static final ByteCache _dataCache = ByteCache.getInstance(512, PREPROCESSED_SIZE); protected static final ByteCache _ivCache = ByteCache.getInstance(128, IV_SIZE); + protected static final ByteCache _hashCache = ByteCache.getInstance(128, Hash.HASH_LENGTH); public TrivialPreprocessor(I2PAppContext ctx) { _context = ctx; @@ -104,7 +105,11 @@ public class TrivialPreprocessor implements TunnelGateway.QueuePreprocessor { // payload ready, now H(instructions+payload+IV) System.arraycopy(iv, 0, fragments, fragmentLength, IV_SIZE); - Hash h = _context.sha().calculateHash(fragments, 0, fragmentLength + IV_SIZE); + + ByteArray hashBuf = _hashCache.acquire(); + //Hash h = _context.sha().calculateHash(fragments, 0, fragmentLength + IV_SIZE); + _context.sha().calculateHash(fragments, 0, fragmentLength + IV_SIZE, hashBuf.getData(), 0); + //Hash h = _context.sha().calculateHash(target, 0, offset + IV_SIZE); //_log.debug("before shift: " + Base64.encode(target)); // now shiiiiiift @@ -121,10 +126,12 @@ public class TrivialPreprocessor implements TunnelGateway.QueuePreprocessor { int offset = 0; System.arraycopy(iv, 0, fragments, offset, IV_SIZE); offset += IV_SIZE; - System.arraycopy(h.getData(), 0, fragments, offset, 4); + //System.arraycopy(h.getData(), 0, fragments, offset, 4); + System.arraycopy(hashBuf.getData(), 0, fragments, offset, 4); offset += 4; //_log.debug("before pad : " + Base64.encode(target)); + _hashCache.release(hashBuf); _ivCache.release(ivBuf); // fits in a single message, so may be smaller than the full size diff --git a/router/java/src/net/i2p/router/tunnel/TunnelDispatcher.java b/router/java/src/net/i2p/router/tunnel/TunnelDispatcher.java index d13e49ca4..5d76897d9 100644 --- a/router/java/src/net/i2p/router/tunnel/TunnelDispatcher.java +++ b/router/java/src/net/i2p/router/tunnel/TunnelDispatcher.java @@ -349,35 +349,35 @@ public class TunnelDispatcher implements Service { long before = _context.clock().now(); TunnelParticipant participant = null; synchronized (_participants) { - participant = (TunnelParticipant)_participants.get(msg.getTunnelId()); + participant = (TunnelParticipant)_participants.get(msg.getTunnelIdObj()); } if (participant != null) { // we are either just a random participant or the inbound endpoint if (_log.shouldLog(Log.DEBUG)) _log.debug("dispatch to participant " + participant + ": " + msg.getUniqueId() + " from " + recvFrom.toBase64().substring(0,4)); - _context.messageHistory().tunnelDispatched(msg.getUniqueId(), msg.getTunnelId().getTunnelId(), "participant"); + _context.messageHistory().tunnelDispatched(msg.getUniqueId(), msg.getTunnelId(), "participant"); participant.dispatch(msg, recvFrom); _context.statManager().addRateData("tunnel.dispatchParticipant", 1, 0); } else { OutboundTunnelEndpoint endpoint = null; synchronized (_outboundEndpoints) { - endpoint = (OutboundTunnelEndpoint)_outboundEndpoints.get(msg.getTunnelId()); + endpoint = (OutboundTunnelEndpoint)_outboundEndpoints.get(msg.getTunnelIdObj()); } if (endpoint != null) { // we are the outobund endpoint if (_log.shouldLog(Log.DEBUG)) _log.debug("dispatch where we are the outbound endpoint: " + endpoint + ": " + msg + " from " + recvFrom.toBase64().substring(0,4)); - _context.messageHistory().tunnelDispatched(msg.getUniqueId(), msg.getTunnelId().getTunnelId(), "outbound endpoint"); + _context.messageHistory().tunnelDispatched(msg.getUniqueId(), msg.getTunnelId(), "outbound endpoint"); endpoint.dispatch(msg, recvFrom); _context.statManager().addRateData("tunnel.dispatchEndpoint", 1, 0); } else { - _context.messageHistory().droppedTunnelDataMessageUnknown(msg.getUniqueId(), msg.getTunnelId().getTunnelId()); + _context.messageHistory().droppedTunnelDataMessageUnknown(msg.getUniqueId(), msg.getTunnelId()); int level = (_context.router().getUptime() > 10*60*1000 ? Log.ERROR : Log.WARN); if (_log.shouldLog(level)) - _log.log(level, "no matching participant/endpoint for id=" + msg.getTunnelId().getTunnelId() + _log.log(level, "no matching participant/endpoint for id=" + msg.getTunnelId() + " expiring in " + DataHelper.formatDuration(msg.getMessageExpiration()-_context.clock().now()) + ": existing = " + _participants.size() + " / " + _outboundEndpoints.size()); } @@ -410,8 +410,9 @@ public class TunnelDispatcher implements Service { + msg.getMessage().getClass().getName()); return; } - _context.messageHistory().tunnelDispatched("message " + msg.getUniqueId() + "/" + msg.getMessage().getUniqueId() + " on tunnel " - + msg.getTunnelId().getTunnelId() + " as inbound gateway"); + //_context.messageHistory().tunnelDispatched("message " + msg.getUniqueId() + "/" + msg.getMessage().getUniqueId() + " on tunnel " + // + msg.getTunnelId().getTunnelId() + " as inbound gateway"); + _context.messageHistory().tunnelDispatched(msg.getUniqueId(), msg.getMessage().getUniqueId(), msg.getTunnelId().getTunnelId(), "inbound gateway"); gw.add(msg); _context.statManager().addRateData("tunnel.dispatchInbound", 1, 0); } else { diff --git a/router/java/src/net/i2p/router/tunnel/pool/RequestTunnelJob.java b/router/java/src/net/i2p/router/tunnel/pool/RequestTunnelJob.java index ab6dd977e..2aa6aeca5 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/RequestTunnelJob.java +++ b/router/java/src/net/i2p/router/tunnel/pool/RequestTunnelJob.java @@ -104,7 +104,7 @@ public class RequestTunnelJob extends JobImpl { // inbound tunnel, which means we are the first person asked, and if // it is a zero hop tunnel, then we are also the last person asked - long id = getContext().random().nextLong(TunnelId.MAX_ID_VALUE); + long id = getContext().random().nextLong(TunnelId.MAX_ID_VALUE-1) + 1; _currentConfig.setReceiveTunnelId(DataHelper.toLong(4, id)); if (_config.getLength() > 1) { if (_log.shouldLog(Log.DEBUG))