From 774231f347ffd8f44221372d4237ec60e770a579 Mon Sep 17 00:00:00 2001
From: jrandom <jrandom>
Date: Tue, 28 Sep 2004 20:33:23 +0000
Subject: [PATCH] * started reducing the temporary buffers created within
 various crypto methods , as we've got some pretty heavy GC churn when under
 load.  rough estimate is we allocate 5-8x as much data as we need, copying it
 all over the place before forwarding it (or processing it). this should cut
 down a few of those copies, but not enough yet.  it'd be great to get that
 down to 2x. * lots of logging

---
 core/java/src/net/i2p/I2PAppContext.java      |   2 +-
 core/java/src/net/i2p/crypto/AESEngine.java   |  61 +++----
 .../src/net/i2p/crypto/AESInputStream.java    |  54 +++---
 .../src/net/i2p/crypto/AESOutputStream.java   |  28 ++--
 .../src/net/i2p/crypto/CryptixAESEngine.java  | 156 +++++++++---------
 .../net/i2p/crypto/DHSessionKeyBuilder.java   |   6 +-
 .../src/net/i2p/crypto/ElGamalAESEngine.java  |   6 +-
 core/java/src/net/i2p/data/DataHelper.java    |  30 +++-
 .../java/test/net/i2p/crypto/AES256Bench.java |  22 ++-
 .../net/i2p/crypto/ElGamalAESEngineTest.java  |   6 +-
 .../src/net/i2p/data/i2np/TunnelMessage.java  |   2 +-
 .../src/net/i2p/router/InNetMessagePool.java  |   7 +
 .../src/net/i2p/router/OutNetMessagePool.java |   7 +
 .../message/HandleTunnelMessageJob.java       |   4 +-
 .../router/message/SendTunnelMessageJob.java  |   2 +-
 .../transport/tcp/ConnectionBuilder.java      |   8 +-
 .../transport/tcp/ConnectionHandler.java      |   6 +-
 .../transport/tcp/ConnectionRunner.java       |   5 +
 .../router/transport/tcp/MessageHandler.java  |   7 +
 .../router/transport/tcp/TCPConnection.java   |   1 +
 20 files changed, 245 insertions(+), 175 deletions(-)

diff --git a/core/java/src/net/i2p/I2PAppContext.java b/core/java/src/net/i2p/I2PAppContext.java
index b782378e26..c36f45eca1 100644
--- a/core/java/src/net/i2p/I2PAppContext.java
+++ b/core/java/src/net/i2p/I2PAppContext.java
@@ -284,7 +284,7 @@ public class I2PAppContext {
      * matter.  Though for the crazy people out there, we do expose a way to 
      * disable it.
      */
-    public AESEngine AESEngine() {
+    public AESEngine aes() {
         if (!_AESEngineInitialized) initializeAESEngine();
         return _AESEngine;
     }
diff --git a/core/java/src/net/i2p/crypto/AESEngine.java b/core/java/src/net/i2p/crypto/AESEngine.java
index 1d1068ae38..2d474bffb0 100644
--- a/core/java/src/net/i2p/crypto/AESEngine.java
+++ b/core/java/src/net/i2p/crypto/AESEngine.java
@@ -37,21 +37,16 @@ public class AESEngine {
     
     /** Encrypt the payload with the session key
      * @param payload data to be encrypted
+     * @param payloadIndex index into the payload to start encrypting
+     * @param out where to store the result
+     * @param outIndex where in out to start writing
      * @param sessionKey private esession key to encrypt to
-     * @param initializationVector IV for CBC
-     * @return encrypted data
+     * @param iv IV for CBC
+     * @param length how much data to encrypt
      */
-    public byte[] encrypt(byte payload[], SessionKey sessionKey, byte initializationVector[]) {
-        if ((initializationVector == null) || (payload == null) || (sessionKey == null)
-            || (initializationVector.length != 16)) return null;
-
-        byte cyphertext[] = null;
-        if ((payload.length % 16) == 0) 
-            cyphertext = new byte[payload.length];
-        else
-            cyphertext = new byte[payload.length + (16 - (payload.length % 16))];
-        System.arraycopy(payload, 0, cyphertext, 0, payload.length);
-        return cyphertext;
+    public void encrypt(byte payload[], int payloadIndex, byte out[], int outIndex, SessionKey sessionKey, byte iv[], int length) {
+        System.arraycopy(payload, payloadIndex, out, outIndex, length);
+        _log.warn("Warning: AES is disabled");
     }
 
     public byte[] safeEncrypt(byte payload[], SessionKey sessionKey, byte iv[], int paddedSize) {
@@ -72,13 +67,17 @@ public class AESEngine {
             _log.error("Error writing data", dfe);
             return null;
         }
-        return encrypt(baos.toByteArray(), sessionKey, iv);
+        byte orig[] = baos.toByteArray();
+        byte rv[] = new byte[orig.length];
+        encrypt(orig, 0, rv, 0, sessionKey, iv, rv.length);
+        return rv;
     }
 
     public byte[] safeDecrypt(byte payload[], SessionKey sessionKey, byte iv[]) {
         if ((iv == null) || (payload == null) || (sessionKey == null) || (iv.length != 16)) return null;
 
-        byte decr[] = decrypt(payload, sessionKey, iv);
+        byte decr[] = new byte[payload.length];
+        decrypt(payload, 0, decr, 0, sessionKey, iv, payload.length);
         if (decr == null) {
             _log.error("Error decrypting the data - payload " + payload.length + " decrypted to null");
             return null;
@@ -110,19 +109,19 @@ public class AESEngine {
         }
     }
 
-    /** decrypt the data with the session key provided
-     * @param cyphertext encrypted data
-     * @param sessionKey private session key
-     * @param initializationVector IV for CBC
-     * @return unencrypted data
-     */
-    public byte[] decrypt(byte cyphertext[], SessionKey sessionKey, byte initializationVector[]) {
-        if ((initializationVector == null) || (cyphertext == null) || (sessionKey == null)
-            || (initializationVector.length != 16)) return null;
 
-        byte payload[] = new byte[cyphertext.length];
+    /** Decrypt the data with the session key
+     * @param payload data to be decrypted
+     * @param payloadIndex index into the payload to start decrypting
+     * @param out where to store the cleartext
+     * @param outIndex where in out to start writing
+     * @param sessionKey private session key to decrypt to
+     * @param iv IV for CBC
+     * @param length how much data to decrypt
+     */
+    public void decrypt(byte payload[], int payloadIndex, byte out[], int outIndex, SessionKey sessionKey, byte iv[], int length) {
+        System.arraycopy(payload, payloadIndex, out, outIndex, length);
         _log.warn("Warning: AES is disabled");
-        return cyphertext;
     }
 
     public static void main(String args[]) {
@@ -133,14 +132,16 @@ public class AESEngine {
 
         byte sbuf[] = new byte[16];
         RandomSource.getInstance().nextBytes(sbuf);
-        byte se[] = ctx.AESEngine().encrypt(sbuf, key, iv);
-        byte sd[] = ctx.AESEngine().decrypt(se, key, iv);
+        byte se[] = new byte[16];
+        ctx.aes().encrypt(sbuf, 0, se, 0, key, iv, sbuf.length);
+        byte sd[] = new byte[16];
+        ctx.aes().decrypt(se, 0, sd, 0, key, iv, se.length);
         ctx.logManager().getLog(AESEngine.class).debug("Short test: " + DataHelper.eq(sd, sbuf));
 
         byte lbuf[] = new byte[1024];
         RandomSource.getInstance().nextBytes(sbuf);
-        byte le[] = ctx.AESEngine().safeEncrypt(lbuf, key, iv, 2048);
-        byte ld[] = ctx.AESEngine().safeDecrypt(le, key, iv);
+        byte le[] = ctx.aes().safeEncrypt(lbuf, key, iv, 2048);
+        byte ld[] = ctx.aes().safeDecrypt(le, key, iv);
         ctx.logManager().getLog(AESEngine.class).debug("Long test: " + DataHelper.eq(ld, lbuf));
     }
 }
\ No newline at end of file
diff --git a/core/java/src/net/i2p/crypto/AESInputStream.java b/core/java/src/net/i2p/crypto/AESInputStream.java
index e5012cffc2..2e1220e77f 100644
--- a/core/java/src/net/i2p/crypto/AESInputStream.java
+++ b/core/java/src/net/i2p/crypto/AESInputStream.java
@@ -230,22 +230,21 @@ public class AESInputStream extends FilterInputStream {
                 _log.info(encrypted.length + " bytes makes up " + numBlocks + " blocks to decrypt normally");
         }
 
-        byte block[] = new byte[BLOCK_SIZE];
         for (int i = 0; i < numBlocks; i++) {
-            System.arraycopy(encrypted, i * BLOCK_SIZE, block, 0, BLOCK_SIZE);
-            byte decrypted[] = _context.AESEngine().decrypt(block, _key, _lastBlock);
-            byte data[] = DataHelper.xor(decrypted, _lastBlock);
-            int cleaned[] = stripPadding(data);
-            for (int j = 0; j < cleaned.length; j++) {
-                if (cleaned[j] <= 0) {
-                    cleaned[j] += 256;
-                    //_log.error("(modified: " + cleaned[j] + ")");
-                }
-                _readyBuf.add(new Integer(cleaned[j]));
+            _context.aes().decrypt(encrypted, i * BLOCK_SIZE, encrypted, i * BLOCK_SIZE, _key, _lastBlock, BLOCK_SIZE);
+            DataHelper.xor(encrypted, i * BLOCK_SIZE, _lastBlock, 0, encrypted, i * BLOCK_SIZE, BLOCK_SIZE);
+            int payloadBytes = countBlockPayload(encrypted, i * BLOCK_SIZE);
+            
+            for (int j = 0; j < payloadBytes; j++) {
+                int c = encrypted[j + i * BLOCK_SIZE];
+                if (c <= 0)
+                    c += 256;
+                _readyBuf.add(new Integer(c));
             }
-            _cumulativePrepared += cleaned.length;
-            //_log.debug("Updating last block for inputStream");
-            System.arraycopy(decrypted, 0, _lastBlock, 0, BLOCK_SIZE);
+            _cumulativePaddingStripped += BLOCK_SIZE - payloadBytes;
+            _cumulativePrepared += payloadBytes;
+            
+            System.arraycopy(encrypted, i * BLOCK_SIZE, _lastBlock, 0, BLOCK_SIZE);
         }
 
         int remaining = encrypted.length % BLOCK_SIZE;
@@ -263,6 +262,9 @@ public class AESInputStream extends FilterInputStream {
     }
 
     /**
+     * How many non-padded bytes are there in the block starting at the given
+     * location.
+     *
      * PKCS#5 specifies the padding for the block has the # of padding bytes
      * located in the last byte of the block, and each of the padding bytes are
      * equal to that value.  
@@ -275,31 +277,29 @@ public class AESInputStream extends FilterInputStream {
      *
      * We use 16 byte blocks in this AES implementation
      *
+     * @throws IOException if the padding is invalid
      */
-    private int[] stripPadding(byte data[]) throws IOException {
-        int numPadBytes = data[data.length - 1];
-        if ((numPadBytes >= data.length) || (numPadBytes <= 0)) {
+    private int countBlockPayload(byte data[], int startIndex) throws IOException {
+        int numPadBytes = data[startIndex + BLOCK_SIZE - 1];
+        if ((numPadBytes >= BLOCK_SIZE) || (numPadBytes <= 0)) {
             if (_log.shouldLog(Log.DEBUG))
-                _log.debug("stripPadding from block " + DataHelper.toHexString(data) + " (" + data.length + "bytes): "
+                _log.debug("countBlockPayload on block index " + startIndex 
                            + numPadBytes + " is an invalid # of pad bytes");
             throw new IOException("Invalid number of pad bytes (" + numPadBytes 
-                                  + ") for " + data.length + " bytes");
+                                  + ") for " + startIndex + " index");
         }
         
-        int rv[] = new int[data.length - numPadBytes];
         // optional, but a really good idea: verify the padding
         if (true) {
-            for (int i = data.length - numPadBytes; i < data.length; i++) {
-                if (data[i] != (byte) numPadBytes) { 
+            for (int i = BLOCK_SIZE - numPadBytes; i < BLOCK_SIZE; i++) {
+                if (data[startIndex + i] != (byte) numPadBytes) { 
                     throw new IOException("Incorrect padding on decryption: data[" + i
-                                          + "] = " + data[i] + " not " + numPadBytes); 
+                                          + "] = " + data[startIndex + i] + " not " + numPadBytes); 
                 }
             }
         }
-        for (int i = 0; i < rv.length; i++)
-            rv[i] = data[i];
-        _cumulativePaddingStripped += numPadBytes;
-        return rv;
+        
+        return BLOCK_SIZE - numPadBytes;
     }
 
     int remainingBytes() {
diff --git a/core/java/src/net/i2p/crypto/AESOutputStream.java b/core/java/src/net/i2p/crypto/AESOutputStream.java
index f50b39f6c0..4b6a63b3ab 100644
--- a/core/java/src/net/i2p/crypto/AESOutputStream.java
+++ b/core/java/src/net/i2p/crypto/AESOutputStream.java
@@ -106,18 +106,16 @@ public class AESOutputStream extends FilterOutputStream {
         int numBlocks = src.length / (BLOCK_SIZE - 1);
 
         byte block[] = new byte[BLOCK_SIZE];
-        block[BLOCK_SIZE - 1] = 0x01; // the padding byte for "full" blocks
         for (int i = 0; i < numBlocks; i++) {
-            System.arraycopy(src, i * 15, block, 0, 15);
-            byte data[] = DataHelper.xor(block, _lastBlock);
-            byte encrypted[] = _context.AESEngine().encrypt(data, _key, _lastBlock);
-            _cumulativeWritten += encrypted.length;
+            DataHelper.xor(src, i * 15, _lastBlock, 0, block, 0, 15);
+            // the padding byte for "full" blocks
+            block[BLOCK_SIZE - 1] = (byte)(_lastBlock[BLOCK_SIZE - 1] ^ 0x01); 
+            _context.aes().encrypt(block, 0, block, 0, _key, _lastBlock, BLOCK_SIZE);
             if (_log.shouldLog(Log.DEBUG))
-                _log.debug("Padding block " + i + " of " + numBlocks + " with 1 byte.  orig= " 
-                           + DataHelper.toHexString(data) + " (size=" + data.length + ") encrypted= " 
-                           + DataHelper.toHexString(encrypted) + " (size=" + encrypted.length + ")");
-            out.write(encrypted);
-            System.arraycopy(encrypted, encrypted.length - BLOCK_SIZE, _lastBlock, 0, BLOCK_SIZE);
+                _log.debug("Padding block " + i + " of " + numBlocks + " with 1 byte");
+            out.write(block);
+            System.arraycopy(block, 0, _lastBlock, 0, BLOCK_SIZE);
+            _cumulativeWritten += BLOCK_SIZE;
             _cumulativePadding++;
         }
 
@@ -129,12 +127,12 @@ public class AESOutputStream extends FilterOutputStream {
                 _log.debug("Padding " + src.length + " with " + paddingBytes + " bytes in " + numBlocks + " blocks");
             System.arraycopy(src, numBlocks * 15, block, 0, remainingBytes);
             Arrays.fill(block, remainingBytes, BLOCK_SIZE, (byte) paddingBytes);
-            byte data[] = DataHelper.xor(block, _lastBlock);
-            byte encrypted[] = _context.AESEngine().encrypt(data, _key, _lastBlock);
-            out.write(encrypted);
-            System.arraycopy(encrypted, encrypted.length - BLOCK_SIZE, _lastBlock, 0, BLOCK_SIZE);
+            DataHelper.xor(block, 0, _lastBlock, 0, block, 0, BLOCK_SIZE);
+            _context.aes().encrypt(block, 0, block, 0, _key, _lastBlock, BLOCK_SIZE);
+            out.write(block);
+            System.arraycopy(block, 0, _lastBlock, 0, BLOCK_SIZE);
             _cumulativePadding += paddingBytes;
-            _cumulativeWritten += encrypted.length;
+            _cumulativeWritten += BLOCK_SIZE;
         }
     }
 
diff --git a/core/java/src/net/i2p/crypto/CryptixAESEngine.java b/core/java/src/net/i2p/crypto/CryptixAESEngine.java
index 51658f77bd..8b1454fef6 100644
--- a/core/java/src/net/i2p/crypto/CryptixAESEngine.java
+++ b/core/java/src/net/i2p/crypto/CryptixAESEngine.java
@@ -12,6 +12,7 @@ package net.i2p.crypto;
 import java.security.InvalidKeyException;
 
 import net.i2p.I2PAppContext;
+import net.i2p.data.DataHelper;
 import net.i2p.data.SessionKey;
 import net.i2p.util.Log;
 
@@ -33,104 +34,63 @@ public class CryptixAESEngine extends AESEngine {
         super(context);
         _log = context.logManager().getLog(CryptixAESEngine.class);
     }
-
-    public byte[] encrypt(byte payload[], SessionKey sessionKey, byte initializationVector[]) {
-        if ((initializationVector == null) || (payload == null) || (payload.length <= 0) || (sessionKey == null)
-            || (initializationVector.length != 16)) return null;
+    
+    public void encrypt(byte payload[], int payloadIndex, byte out[], int outIndex, SessionKey sessionKey, byte iv[], int length) {
+        if ( (payload == null) || (out == null) || (sessionKey == null) || (iv == null) || (iv.length != 16) ) 
+            throw new NullPointerException("invalid args to aes");
+        if (payload.length < payloadIndex + length)
+            throw new IllegalArgumentException("Payload is too short");
+        if (out.length < outIndex + length)
+            throw new IllegalArgumentException("Output is too short");
+        if (length <= 0) 
+            throw new IllegalArgumentException("Length is too small");
+        if (length % 16 != 0) 
+            throw new IllegalArgumentException("Only lengths mod 16 are supported here");
 
         if (USE_FAKE_CRYPTO) {
             _log.warn("AES Crypto disabled!  Using trivial XOR");
-            byte rv[] = new byte[payload.length];
-            for (int i = 0; i < rv.length; i++)
-                rv[i] = (byte) (payload[i] ^ FAKE_KEY);
-            return rv;
-        }
-
-        int numblock = payload.length / 16;
-        if (payload.length % 16 != 0) numblock++;
-        byte[][] plain = new byte[numblock][16];
-        for (int x = 0; x < numblock; x++) {
-            for (int y = 0; y < 16; y++) {
-                plain[x][y] = payload[x * 16 + y];
-            }
+            System.arraycopy(payload, payloadIndex, out, outIndex, length);
+            return;
         }
 
-        byte[][] cipher = new byte[numblock][16];
-        cipher[0] = encrypt(xor(initializationVector, plain[0]), sessionKey);
+        int numblock = length / 16;
+        
+        DataHelper.xor(iv, 0, payload, payloadIndex, out, outIndex, 16);
+        encryptBlock(out, outIndex, sessionKey, out, outIndex);
         for (int x = 1; x < numblock; x++) {
-            cipher[x] = encrypt(xor(cipher[x - 1], plain[x]), sessionKey);
+            DataHelper.xor(out, outIndex + (x-1) * 16, payload, payloadIndex + x * 16, out, outIndex + x * 16, 16);
+            encryptBlock(out, outIndex + x * 16, sessionKey, out, outIndex + x * 16);
         }
-
-        byte[] ret = new byte[numblock * 16];
-        for (int x = 0; x < numblock; x++) {
-            for (int y = 0; y < 16; y++) {
-                ret[x * 16 + y] = cipher[x][y];
-            }
-        }
-
-        return ret;
     }
 
-    public byte[] decrypt(byte payload[], SessionKey sessionKey, byte initializationVector[]) {
-        if ((initializationVector == null) || (payload == null) || (payload.length <= 0) || (sessionKey == null)
-            || (initializationVector.length != 16)) return null;
+    public void decrypt(byte payload[], int payloadIndex, byte out[], int outIndex, SessionKey sessionKey, byte iv[], int length) {
+        if ((iv== null) || (payload == null) || (payload.length <= 0) || (sessionKey == null)
+            || (iv.length != 16)) 
+            throw new IllegalArgumentException("bad setup");
 
         if (USE_FAKE_CRYPTO) {
             _log.warn("AES Crypto disabled!  Using trivial XOR");
-            byte rv[] = new byte[payload.length];
-            for (int i = 0; i < rv.length; i++)
-                rv[i] = (byte) (payload[i] ^ FAKE_KEY);
-            return rv;
+            System.arraycopy(payload, payloadIndex, out, outIndex, length);
+            return ;
         }
 
         int numblock = payload.length / 16;
         if (payload.length % 16 != 0) numblock++;
-        byte[][] cipher = new byte[numblock][16];
-        for (int x = 0; x < numblock; x++) {
-            for (int y = 0; y < 16; y++) {
-                cipher[x][y] = payload[x * 16 + y];
-            }
-        }
 
-        byte[][] plain = new byte[numblock][16];
-        plain[0] = xor(decrypt(cipher[0], sessionKey), initializationVector);
+        decryptBlock(payload, 0, sessionKey, out, 0);
+		DataHelper.xor(out, 0, iv, 0, out, 0, 16);
         for (int x = 1; x < numblock; x++) {
-            plain[x] = xor(decrypt(cipher[x], sessionKey), cipher[x - 1]);
-        }
-
-        byte[] ret = new byte[numblock * 16];
-        for (int x = 0; x < numblock; x++) {
-            for (int y = 0; y < 16; y++) {
-                ret[x * 16 + y] = plain[x][y];
-            }
+            decryptBlock(payload, x * 16, sessionKey, out, x * 16);
+            DataHelper.xor(out, x * 16, payload, (x - 1) * 16, out, x * 16, 16);
         }
-
-        return ret;
-    }
-
-    final static byte[] xor(byte[] a, byte[] b) {
-        if ((a == null) || (b == null) || (a.length != b.length)) return null;
-        byte[] ret = new byte[a.length];
-        for (int x = 0; x < a.length; x++) {
-            ret[x] = (byte) (a[x] ^ b[x]);
-        }
-        return ret;
     }
 
-    /** Encrypt the payload with the session key
-     * @param payload data to be encrypted
-     * @param sessionKey private esession key to encrypt to
-     * @return encrypted data
-     */
-    final byte[] encrypt(byte payload[], SessionKey sessionKey) {
+    final void encryptBlock(byte payload[], int inIndex, SessionKey sessionKey, byte out[], int outIndex) {
         try {
             Object key = CryptixRijndael_Algorithm.makeKey(sessionKey.getData(), 16);
-            byte rv[] = new byte[payload.length];
-            CryptixRijndael_Algorithm.blockEncrypt(payload, rv, 0, key, 16);
-            return rv;
+            CryptixRijndael_Algorithm.blockEncrypt(payload, out, inIndex, outIndex, key, 16);
         } catch (InvalidKeyException ike) {
             _log.error("Invalid key", ike);
-            return null;
         }
     }
 
@@ -139,15 +99,55 @@ public class CryptixAESEngine extends AESEngine {
      * @param sessionKey private session key
      * @return unencrypted data
      */
-    final byte[] decrypt(byte payload[], SessionKey sessionKey) {
+    final void decryptBlock(byte payload[], int inIndex, SessionKey sessionKey, byte rv[], int outIndex) {
         try {
             Object key = CryptixRijndael_Algorithm.makeKey(sessionKey.getData(), 16);
-            byte rv[] = new byte[payload.length];
-            CryptixRijndael_Algorithm.blockDecrypt(payload, rv, 0, key, 16);
-            return rv;
+            CryptixRijndael_Algorithm.blockDecrypt(payload, rv, inIndex, outIndex, key, 16);
         } catch (InvalidKeyException ike) {
             _log.error("Invalid key", ike);
-            return null;
         }
     }
+    
+    public static void main(String args[]) {
+        I2PAppContext ctx = new I2PAppContext();
+        try {
+            testEDBlock(ctx);
+            testED(ctx);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
+    }
+    private static void testED(I2PAppContext ctx) {
+        SessionKey key = ctx.keyGenerator().generateSessionKey();
+        byte iv[] = new byte[16];
+        byte orig[] = new byte[128];
+        byte encrypted[] = new byte[128];
+        byte decrypted[] = new byte[128];
+        ctx.random().nextBytes(iv);
+        ctx.random().nextBytes(orig);
+        CryptixAESEngine aes = new CryptixAESEngine(ctx);
+        aes.encrypt(orig, 0, encrypted, 0, key, iv, orig.length);
+        aes.decrypt(encrypted, 0, decrypted, 0, key, iv, encrypted.length);
+        if (!DataHelper.eq(decrypted,orig))
+            throw new RuntimeException("full D(E(orig)) != orig");
+        else
+            System.out.println("full D(E(orig)) == orig");
+    }
+    private static void testEDBlock(I2PAppContext ctx) {
+        SessionKey key = ctx.keyGenerator().generateSessionKey();
+        byte iv[] = new byte[16];
+        byte orig[] = new byte[16];
+        byte encrypted[] = new byte[16];
+        byte decrypted[] = new byte[16];
+        ctx.random().nextBytes(iv);
+        ctx.random().nextBytes(orig);
+        CryptixAESEngine aes = new CryptixAESEngine(ctx);
+        aes.encryptBlock(orig, 0, key, encrypted, 0);
+        aes.decryptBlock(encrypted, 0, key, decrypted, 0);
+        if (!DataHelper.eq(decrypted,orig))
+            throw new RuntimeException("block D(E(orig)) != orig");
+        else
+            System.out.println("block D(E(orig)) == orig");
+    }
 }
\ No newline at end of file
diff --git a/core/java/src/net/i2p/crypto/DHSessionKeyBuilder.java b/core/java/src/net/i2p/crypto/DHSessionKeyBuilder.java
index 5b205541a5..804aff1af7 100644
--- a/core/java/src/net/i2p/crypto/DHSessionKeyBuilder.java
+++ b/core/java/src/net/i2p/crypto/DHSessionKeyBuilder.java
@@ -352,8 +352,10 @@ public class DHSessionKeyBuilder {
             byte iv[] = new byte[16];
             RandomSource.getInstance().nextBytes(iv);
             String origVal = "1234567890123456"; // 16 bytes max using AESEngine
-            byte enc[] = ctx.AESEngine().encrypt(origVal.getBytes(), key1, iv);
-            byte dec[] = ctx.AESEngine().decrypt(enc, key2, iv);
+            byte enc[] = new byte[16];
+            byte dec[] = new byte[16];
+            ctx.aes().encrypt(origVal.getBytes(), 0, enc, 0, key1, iv, 16);
+            ctx.aes().decrypt(enc, 0, dec, 0, key2, iv, 16);
             String tranVal = new String(dec);
             if (origVal.equals(tranVal))
                 _log.debug("**Success: D(E(val)) == val");
diff --git a/core/java/src/net/i2p/crypto/ElGamalAESEngine.java b/core/java/src/net/i2p/crypto/ElGamalAESEngine.java
index bf9ae0e4c0..1b6e501ee3 100644
--- a/core/java/src/net/i2p/crypto/ElGamalAESEngine.java
+++ b/core/java/src/net/i2p/crypto/ElGamalAESEngine.java
@@ -248,7 +248,8 @@ public class ElGamalAESEngine {
                                   SessionKey foundKey) throws DataFormatException {
         //_log.debug("iv for decryption: " + DataHelper.toString(iv, 16));	
         //_log.debug("decrypting AES block.  encr.length = " + (encrypted == null? -1 : encrypted.length) + " sentTag: " + DataHelper.toString(sentTag, 32));
-        byte decrypted[] = _context.AESEngine().decrypt(encrypted, key, iv);
+        byte decrypted[] = new byte[encrypted.length];
+        _context.aes().decrypt(encrypted, 0, decrypted, 0, key, iv, encrypted.length);
         //Hash h = _context.sha().calculateHash(decrypted);
         //_log.debug("Hash of entire aes block after decryption: \n" + DataHelper.toString(h.getData(), 32));
         try {
@@ -503,7 +504,8 @@ public class ElGamalAESEngine {
             byte aesUnencr[] = aesSrc.toByteArray();
             //Hash h = _context.sha().calculateHash(aesUnencr);
             //_log.debug("Hash of entire aes block before encryption: (len=" + aesUnencr.length + ")\n" + DataHelper.toString(h.getData(), 32));
-            byte aesEncr[] = _context.AESEngine().encrypt(aesUnencr, key, iv);
+            byte aesEncr[] = new byte[aesUnencr.length];
+            _context.aes().encrypt(aesUnencr, 0, aesEncr, 0, key, iv, aesEncr.length);
             //_log.debug("Encrypted length: " + aesEncr.length);
             return aesEncr;
         } catch (IOException ioe) {
diff --git a/core/java/src/net/i2p/data/DataHelper.java b/core/java/src/net/i2p/data/DataHelper.java
index 84f69aa018..b2e5b4ad0e 100644
--- a/core/java/src/net/i2p/data/DataHelper.java
+++ b/core/java/src/net/i2p/data/DataHelper.java
@@ -467,12 +467,38 @@ public class DataHelper {
 
     public final static byte[] xor(byte lhs[], byte rhs[]) {
         if ((lhs == null) || (rhs == null) || (lhs.length != rhs.length)) return null;
+        byte rv[] = new byte[lhs.length];
+        
         byte diff[] = new byte[lhs.length];
-        for (int i = 0; i < lhs.length; i++)
-            diff[i] = (byte) (lhs[i] ^ rhs[i]);
+        xor(lhs, 0, rhs, 0, diff, 0, lhs.length);
         return diff;
     }
 
+    /**
+     * xor the lhs with the rhs, storing the result in out.  
+     *
+     * @param lhs one of the source arrays
+     * @param startLeft starting index in the lhs array to begin the xor
+     * @param rhs the other source array
+     * @param startRight starting index in the rhs array to begin the xor
+     * @param out output array
+     * @param startOut starting index in the out array to store the result
+     * @param len how many bytes into the various arrays to xor
+     */
+    public final static void xor(byte lhs[], int startLeft, byte rhs[], int startRight, byte out[], int startOut, int len) {
+        if ( (lhs == null) || (rhs == null) || (out == null) )
+            throw new NullPointerException("Invalid params to xor (" + lhs + ", " + rhs + ", " + out + ")");
+        if (lhs.length < startLeft + len)
+            throw new IllegalArgumentException("Left hand side is too short");
+        if (rhs.length < startRight + len)
+            throw new IllegalArgumentException("Right hand side is too short");
+        if (out.length < startOut + len)
+            throw new IllegalArgumentException("Result is too short");
+        
+        for (int i = 0; i < len; i++)
+            out[startOut + i] = (byte) (lhs[startLeft + i] ^ rhs[startRight + i]);
+    }
+
     //
     // The following hashcode helpers make it simpler to write consistently hashing
     // functions for objects based on their value, not JVM memory address
diff --git a/core/java/test/net/i2p/crypto/AES256Bench.java b/core/java/test/net/i2p/crypto/AES256Bench.java
index 60cce7cdf6..f4adb32819 100644
--- a/core/java/test/net/i2p/crypto/AES256Bench.java
+++ b/core/java/test/net/i2p/crypto/AES256Bench.java
@@ -72,22 +72,28 @@ public class AES256Bench {
             iv[x] = (byte)civ[x];
         }
         
-        byte[] e = _context.AESEngine().encrypt(plain, key, iv);
-        byte[] d = _context.AESEngine().decrypt(e, key, iv);
+        byte[] e = new byte[plain.length];
+        _context.aes().encrypt(plain, 0, e, 0, key, iv, plain.length);
+        byte[] d = new byte[e.length];
+        _context.aes().decrypt(e, 0, d, 0, key, iv, d.length);
         boolean same = true;
         for (int x = 0; x < d.length; x++) {
             if (plain[x] != d[x]) {
-                same = false;
+                throw new RuntimeException("Failed decrypt at " + x);
             }
         }
         
         System.out.println("Standard test D(E(value)) == value? " + same);
+        if (!same) throw new RuntimeException("moo");
         
         plain = "1234567890123456".getBytes();
-        e = _context.AESEngine().encrypt(plain, key, iv);
-        d = _context.AESEngine().decrypt(e, key, iv);
+        e = new byte[plain.length];
+        _context.aes().encrypt(plain, 0, e, 0, key, iv, plain.length);
+        d = new byte[e.length];
+        _context.aes().decrypt(e, 0, d, 0, key, iv, d.length);
         same = DataHelper.eq(plain, d);
         System.out.println("Different value test D(E(value)) == value? " + same);
+        if (!same) throw new RuntimeException("moo");
         
         System.out.println();
         System.out.println();
@@ -104,9 +110,11 @@ public class AES256Bench {
             message[i] = (byte)((i%26)+'a');
         for (int x = 0; x < times; x++) {
             long startencrypt = System.currentTimeMillis();
-            e = _context.AESEngine().encrypt(message, key, iv);
+            e = new byte[message.length];
+            d = new byte[e.length];
+            _context.aes().encrypt(message, 0, e, 0, key, iv, message.length);
             long endencryptstartdecrypt = System.currentTimeMillis();
-            d = _context.AESEngine().decrypt(e, key, iv);
+            _context.aes().decrypt(e, 0, d, 0, key, iv, d.length);
             long enddecrypt = System.currentTimeMillis();
             System.out.print(".");
             encrypttime += endencryptstartdecrypt - startencrypt;
diff --git a/core/java/test/net/i2p/crypto/ElGamalAESEngineTest.java b/core/java/test/net/i2p/crypto/ElGamalAESEngineTest.java
index 9fc0a0a051..7bf7c90b84 100644
--- a/core/java/test/net/i2p/crypto/ElGamalAESEngineTest.java
+++ b/core/java/test/net/i2p/crypto/ElGamalAESEngineTest.java
@@ -145,8 +145,10 @@ class ElGamalAESEngineTest {
             String msg = "Hello world01234012345678901234501234567890123450123456789012345";
             h = SHA256Generator.getInstance().calculateHash(msg.getBytes());
             _log.debug("Hash of entire aes block before encryption: \n" + DataHelper.toString(h.getData(), 32));
-            byte aesEncr[] = _context.AESEngine().encrypt(msg.getBytes(), sessionKey, iv);
-            byte aesDecr[] = _context.AESEngine().decrypt(aesEncr, sessionKey, iv);
+            byte aesEncr[] = new byte[msg.getBytes().length];
+            byte aesDecr[] = new byte[aesEncr.length];
+            _context.aes().encrypt(msg.getBytes(), 0, aesEncr, 0, sessionKey, iv, aesEncr.length);
+            _context.aes().decrypt(aesEncr, 0, aesDecr, 0, sessionKey, iv, aesEncr.length);
             h = SHA256Generator.getInstance().calculateHash(aesDecr);
             _log.debug("Hash of entire aes block after decryption: \n" + DataHelper.toString(h.getData(), 32));
             if (msg.equals(new String(aesDecr))) {
diff --git a/router/java/src/net/i2p/data/i2np/TunnelMessage.java b/router/java/src/net/i2p/data/i2np/TunnelMessage.java
index aad4134a50..80da32abfc 100644
--- a/router/java/src/net/i2p/data/i2np/TunnelMessage.java
+++ b/router/java/src/net/i2p/data/i2np/TunnelMessage.java
@@ -89,7 +89,7 @@ public class TunnelMessage extends I2NPMessageImpl {
         if ( (_tunnelId == null) || (_data == null) || (_data.length <= 0) )
             throw new I2NPMessageException("Not enough data to write out");
         
-        ByteArrayOutputStream os = new ByteArrayOutputStream(4096);
+        ByteArrayOutputStream os = new ByteArrayOutputStream(64+_data.length);
         try {
             _tunnelId.writeBytes(os);
             if (_log.shouldLog(Log.DEBUG))
diff --git a/router/java/src/net/i2p/router/InNetMessagePool.java b/router/java/src/net/i2p/router/InNetMessagePool.java
index 07965d0afc..38708d47d9 100644
--- a/router/java/src/net/i2p/router/InNetMessagePool.java
+++ b/router/java/src/net/i2p/router/InNetMessagePool.java
@@ -60,6 +60,13 @@ public class InNetMessagePool {
         I2NPMessage messageBody = msg.getMessage();
         msg.processingComplete();
         Date exp = messageBody.getMessageExpiration();
+        
+        if (_log.shouldLog(Log.INFO))
+                _log.info("Received inbound " 
+                          + " with id " + messageBody.getUniqueId()
+                          + " expiring on " + exp
+                          + " of type " + messageBody.getClass().getName());
+        
         boolean valid = _context.messageValidator().validateMessage(messageBody.getUniqueId(), exp.getTime());
         if (!valid) {
             if (_log.shouldLog(Log.WARN))
diff --git a/router/java/src/net/i2p/router/OutNetMessagePool.java b/router/java/src/net/i2p/router/OutNetMessagePool.java
index dd1efb5d03..389c0636fa 100644
--- a/router/java/src/net/i2p/router/OutNetMessagePool.java
+++ b/router/java/src/net/i2p/router/OutNetMessagePool.java
@@ -41,6 +41,13 @@ public class OutNetMessagePool {
      *
      */
     public void add(OutNetMessage msg) {
+        if (_log.shouldLog(Log.INFO))
+                _log.info("Adding outbound message to " 
+                          + msg.getTarget().getIdentity().getHash().toBase64().substring(0,6)
+                          + " with id " + msg.getMessage().getUniqueId()
+                          + " expiring on " + msg.getMessage().getMessageExpiration()
+                          + " of type " + msg.getMessageType());
+        
         boolean valid = validate(msg);
         if (!valid) return;
         MessageSelector selector = msg.getReplySelector();
diff --git a/router/java/src/net/i2p/router/message/HandleTunnelMessageJob.java b/router/java/src/net/i2p/router/message/HandleTunnelMessageJob.java
index 4de8c4930b..0877f9ec24 100644
--- a/router/java/src/net/i2p/router/message/HandleTunnelMessageJob.java
+++ b/router/java/src/net/i2p/router/message/HandleTunnelMessageJob.java
@@ -415,7 +415,7 @@ public class HandleTunnelMessageJob extends JobImpl {
         byte iv[] = new byte[16];
         Hash h = getContext().sha().calculateHash(key.getData());
         System.arraycopy(h.getData(), 0, iv, 0, iv.length);
-        byte decrypted[] = getContext().AESEngine().safeDecrypt(encryptedMessage, key, iv);
+        byte decrypted[] = getContext().aes().safeDecrypt(encryptedMessage, key, iv);
         if (decrypted == null) {
             if (_log.shouldLog(Log.ERROR))
                 _log.error("Error decrypting the message", getAddedBy());
@@ -429,7 +429,7 @@ public class HandleTunnelMessageJob extends JobImpl {
             byte iv[] = new byte[16];
             Hash h = getContext().sha().calculateHash(key.getData());
             System.arraycopy(h.getData(), 0, iv, 0, iv.length);
-            byte decrypted[] = getContext().AESEngine().safeDecrypt(encryptedInstructions, key, iv);
+            byte decrypted[] = getContext().aes().safeDecrypt(encryptedInstructions, key, iv);
             if (decrypted == null) {
                 if (_log.shouldLog(Log.ERROR))
                     _log.error("Error decrypting the instructions", getAddedBy());
diff --git a/router/java/src/net/i2p/router/message/SendTunnelMessageJob.java b/router/java/src/net/i2p/router/message/SendTunnelMessageJob.java
index 16fbcf6941..5490448856 100644
--- a/router/java/src/net/i2p/router/message/SendTunnelMessageJob.java
+++ b/router/java/src/net/i2p/router/message/SendTunnelMessageJob.java
@@ -396,7 +396,7 @@ public class SendTunnelMessageJob extends JobImpl {
             byte iv[] = new byte[16];
             Hash h = getContext().sha().calculateHash(key.getData());
             System.arraycopy(h.getData(), 0, iv, 0, iv.length);
-            return getContext().AESEngine().safeEncrypt(baos.toByteArray(), key, iv, paddedSize);
+            return getContext().aes().safeEncrypt(baos.toByteArray(), key, iv, paddedSize);
         } catch (IOException ioe) {
             if (_log.shouldLog(Log.ERROR))
                 _log.error("Error writing out data to encrypt", ioe);
diff --git a/router/java/src/net/i2p/router/transport/tcp/ConnectionBuilder.java b/router/java/src/net/i2p/router/transport/tcp/ConnectionBuilder.java
index d65c8fa5a0..dde09f2c23 100644
--- a/router/java/src/net/i2p/router/transport/tcp/ConnectionBuilder.java
+++ b/router/java/src/net/i2p/router/transport/tcp/ConnectionBuilder.java
@@ -307,8 +307,8 @@ public class ConnectionBuilder {
         byte pre[] = new byte[48];
         System.arraycopy(_nonce.getData(), 0, pre, 0, 4);
         System.arraycopy(_connectionTag.getData(), 0, pre, 4, 32);
-        byte encr[] = _context.AESEngine().encrypt(pre, _key, _iv);
-        Hash h = _context.sha().calculateHash(encr);
+        _context.aes().encrypt(pre, 0, pre, 0, _key, _iv, pre.length);
+        Hash h = _context.sha().calculateHash(pre);
         _nextConnectionTag = new ByteArray(h.getData());
     }
     
@@ -638,7 +638,9 @@ public class ConnectionBuilder {
     private void establishComplete() {
         _connectionIn = new BandwidthLimitedInputStream(_context, _rawIn, _actualPeer.getIdentity());
         OutputStream blos = new BandwidthLimitedOutputStream(_context, _rawOut, _actualPeer.getIdentity());
-        _connectionOut = blos;
+        _connectionOut = blos;        
+        //_connectionIn = _rawIn;
+        //_connectionOut = _rawOut;
         
         Hash peer = _actualPeer.getIdentity().getHash();
         _context.netDb().store(peer, _actualPeer);
diff --git a/router/java/src/net/i2p/router/transport/tcp/ConnectionHandler.java b/router/java/src/net/i2p/router/transport/tcp/ConnectionHandler.java
index 8b35003377..e025198ae6 100644
--- a/router/java/src/net/i2p/router/transport/tcp/ConnectionHandler.java
+++ b/router/java/src/net/i2p/router/transport/tcp/ConnectionHandler.java
@@ -328,8 +328,8 @@ public class ConnectionHandler {
         byte pre[] = new byte[48];
         System.arraycopy(_nonce.getData(), 0, pre, 0, 4);
         System.arraycopy(_connectionTag.getData(), 0, pre, 4, 32);
-        byte encr[] = _context.AESEngine().encrypt(pre, _key, _iv);
-        Hash h = _context.sha().calculateHash(encr);
+        _context.aes().encrypt(pre, 0, pre, 0, _key, _iv, pre.length);
+        Hash h = _context.sha().calculateHash(pre);
         _nextConnectionTag = new ByteArray(h.getData());
     }
     
@@ -804,6 +804,8 @@ public class ConnectionHandler {
         _connectionIn = new BandwidthLimitedInputStream(_context, _rawIn, _actualPeer.getIdentity());
         OutputStream blos = new BandwidthLimitedOutputStream(_context, _rawOut, _actualPeer.getIdentity());
         _connectionOut = blos;
+        //_connectionIn = _rawIn;
+        //_connectionOut = _rawOut;
         
         Hash peer = _actualPeer.getIdentity().getHash();
         _context.netDb().store(peer, _actualPeer);
diff --git a/router/java/src/net/i2p/router/transport/tcp/ConnectionRunner.java b/router/java/src/net/i2p/router/transport/tcp/ConnectionRunner.java
index b60f3017e9..82b3cddb2b 100644
--- a/router/java/src/net/i2p/router/transport/tcp/ConnectionRunner.java
+++ b/router/java/src/net/i2p/router/transport/tcp/ConnectionRunner.java
@@ -74,6 +74,11 @@ class ConnectionRunner implements Runnable {
                 out.flush();
                 after = _context.clock().now();
             }
+            if (_log.shouldLog(Log.DEBUG))
+                _log.debug("Just sent message " + msg.getMessageId() + " to " 
+                         + msg.getTarget().getIdentity().getHash().toBase64().substring(0,6)
+                         + " writeTime = " + (after-before) +"ms"
+                         + " lifetime = " + msg.getLifetime() + "ms");
             
             ok = true;
         } catch (IOException ioe) {
diff --git a/router/java/src/net/i2p/router/transport/tcp/MessageHandler.java b/router/java/src/net/i2p/router/transport/tcp/MessageHandler.java
index d0bdeef221..f1ffd8d1f6 100644
--- a/router/java/src/net/i2p/router/transport/tcp/MessageHandler.java
+++ b/router/java/src/net/i2p/router/transport/tcp/MessageHandler.java
@@ -4,12 +4,14 @@ import net.i2p.data.Hash;
 import net.i2p.data.RouterIdentity;
 import net.i2p.data.i2np.I2NPMessageReader;
 import net.i2p.data.i2np.I2NPMessage;
+import net.i2p.util.Log;
 
 /**
  * Receive messages from a message reader and bounce them off to the transport
  * for further enqueueing.
  */
 public class MessageHandler implements I2NPMessageReader.I2NPMessageEventListener {
+    private Log _log;
     private TCPTransport _transport;
     private TCPConnection _con;
     private RouterIdentity _ident;
@@ -20,6 +22,7 @@ public class MessageHandler implements I2NPMessageReader.I2NPMessageEventListene
         _con = con;
         _ident = con.getRemoteRouterIdentity();
         _identHash = _ident.calculateHash();
+        _log = con.getRouterContext().logManager().getLog(MessageHandler.class);
     }
     
     public void disconnected(I2NPMessageReader reader) {
@@ -27,6 +30,10 @@ public class MessageHandler implements I2NPMessageReader.I2NPMessageEventListene
     }
     
     public void messageReceived(I2NPMessageReader reader, I2NPMessage message, long msToRead) {
+        if (_log.shouldLog(Log.DEBUG))
+            _log.debug("Just received message " + message.getUniqueId() + " from " 
+                       + _identHash.toBase64().substring(0,6)
+                       + " readTime = " + msToRead + "ms type = " + message.getClass().getName());
         _transport.messageReceived(message, _ident, _identHash, msToRead, message.getSize());
     }
     
diff --git a/router/java/src/net/i2p/router/transport/tcp/TCPConnection.java b/router/java/src/net/i2p/router/transport/tcp/TCPConnection.java
index a5ef912c34..e8a81d504b 100644
--- a/router/java/src/net/i2p/router/transport/tcp/TCPConnection.java
+++ b/router/java/src/net/i2p/router/transport/tcp/TCPConnection.java
@@ -202,6 +202,7 @@ public class TCPConnection {
 
     /** Have we been closed already? */
     boolean getIsClosed() { return _closed; }
+    RouterContext getRouterContext() { return _context; }
     
     /** 
      * The message was sent.
-- 
GitLab