diff --git a/android/README.txt b/android/README.txt
index 67b8aa30f33128ab1ccbf38fa1b7b84f937adad3..5f633b966bd5cf90dea914203107b46c998811df 100644
--- a/android/README.txt
+++ b/android/README.txt
@@ -28,7 +28,7 @@ ant debug
 ../../android-sdk-linux_86/tools/emulator -avd i2p &
 
 #then wait a couple minutes until the emulator is up
-#then install the I2P app
+#then install the I2P app (ONE TIME ONLY)
 ant install
 
 #then run the debugger
@@ -36,3 +36,5 @@ ant install
 
 #to rebuild and reinstall to emulator:
 ant reinstall
+
+# Now click on the I2P icon on your phone!
diff --git a/android/build.properties b/android/build.properties
new file mode 100644
index 0000000000000000000000000000000000000000..181724115d6453de5705a4d1fd7f7f682a345281
--- /dev/null
+++ b/android/build.properties
@@ -0,0 +1 @@
+application-package=net.i2p.router
diff --git a/android/build.xml b/android/build.xml
index 94356fcf426ca2303b4719007d3357d27cec5d51..6869bce6bd0454592287308aa304505ebcb1f8af 100644
--- a/android/build.xml
+++ b/android/build.xml
@@ -76,6 +76,9 @@
         <mkdir dir="tmp" />
         <unjar src="../build/i2p.jar" dest="tmp/" />
         <delete file="tmp/net/i2p/util/LogWriter.class" />
+        <delete file="tmp/net/i2p/util/SecureDirectory.class" />
+        <delete file="tmp/net/i2p/util/SecureFile.class" />
+        <delete file="tmp/net/i2p/util/SecureFileOutputStream.class" />
         <!-- org.bouncycastle.crypto already in android
              but we need a little trickery because our HMac is incompatible...
              and the libs aren't in the SDK to compile against??? -->
@@ -237,6 +240,7 @@
     <target name="compile" depends="buildrouter, resource-src, aidl">
         <javac encoding="ascii" target="1.5" debug="true" extdirs=""
                 destdir="${out-classes}"
+                includeantruntime="false"
                 bootclasspathref="android.target.classpath">
             <src path="${source-folder}" />
             <src path="${gen-folder}" />
@@ -280,6 +284,12 @@
 
     <!-- Package the application and sign it with a debug key.
          This is the default target when building. It is used for debug. -->
+    <!--
+         I2P when this fails 365 days later because the key expired, delete ~/.android/debug.keystore
+         Then do 'ant uninstall' (since the new key doesn't match the old key)
+         Then do 'ant install'
+         See http://developer.android.com/guide/publishing/app-signing.html for more info
+      -->
     <target name="debug" depends="dex, package-resources">
         <apkbuilder
                 outfolder="${out-folder}"
@@ -327,12 +337,12 @@
         </exec>
     </target>
 
-    <!-- Uinstall the package from the default emulator -->
+    <!-- Uninstall the package from the default emulator -->
     <target name="uninstall">
         <echo>Uninstalling ${application-package} from the default emulator...</echo>
         <exec executable="${adb}" failonerror="true">
             <arg value="uninstall" />
-            <arg path="${application-package}" />
+            <arg value="${application-package}" />
         </exec>
     </target>
     
diff --git a/android/res/raw/router_config b/android/res/raw/router_config
index be361459b66359cd48cc1d5563354b6a1e2d72de..ab95944842da7f96ef0bb2b8f5399f5b24c2e216 100644
--- a/android/res/raw/router_config
+++ b/android/res/raw/router_config
@@ -6,17 +6,27 @@ i2p.dir.pid=/data/data/net.i2p.router/files/tmp
 prng.buffers=2
 router.decayingBloomFilterM=20
 stat.full=false
-i2np.udp.maxConnections=30
+#
 # no I2CP
+#
 i2p.dummyClientFacade=true
-# for now
+#
+##### Transport
+#
+#
+# NTCP
+#
 #i2np.ntcp.enable=false
+i2np.ntcp.maxConnections=8
 #
 # UDP crashes the JVM, don't know why
 #
 i2np.udp.enable=false
+i2np.udp.maxConnections=12
+#
 # no COMM at all!!!
 #i2p.vmCommSystem=true
+#
 # not on android
 i2np.upnp.enable=false
 routerconsole.geoip.enable=false
diff --git a/android/src/net/i2p/router/I2PAndroid.java b/android/src/net/i2p/router/I2PAndroid.java
index 88d522dd632b00279f28caf8038f1817b715296e..262493ec36d0ee9ebd1ee474df65e9d423761554 100644
--- a/android/src/net/i2p/router/I2PAndroid.java
+++ b/android/src/net/i2p/router/I2PAndroid.java
@@ -46,6 +46,7 @@ public class I2PAndroid extends Activity
     {
         System.err.println("onStart called");
         super.onStart();
+//      net.i2p.crypto.DSAEngine.main(null);
         RouterLaunch.main(null);
         System.err.println("Router.main finished");
     }
diff --git a/android/src/net/i2p/util/LogWriter.java b/android/src/net/i2p/util/LogWriter.java
index 0babfab37be87e8b6212c4833860d953dcb721d5..18ba54c8ed3f7f5a1b1b6d1bb507fa5db8171859 100644
--- a/android/src/net/i2p/util/LogWriter.java
+++ b/android/src/net/i2p/util/LogWriter.java
@@ -10,6 +10,7 @@ import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.util.List;
+import java.util.Queue;
 
 /**
  * bridge to android logging
@@ -56,11 +57,21 @@ class LogWriter implements Runnable {
     public void flushRecords() { flushRecords(true); }
     public void flushRecords(boolean shouldWait) {
         try {
-            List records = _manager._removeAll();
+            // zero copy, drain the manager queue directly
+            Queue<LogRecord> records = _manager.getQueue();
             if (records == null) return;
-            for (int i = 0; i < records.size(); i++) {
-                LogRecord rec = (LogRecord) records.get(i);
-                writeRecord(rec);
+            if (!records.isEmpty()) {
+                LogRecord rec;
+                while ((rec = records.poll()) != null) {
+                    writeRecord(rec);
+                }
+                try {
+                    if (_currentOut != null)
+                        _currentOut.flush();
+                } catch (IOException ioe) {
+                    //if (++_diskFullMessageCount < MAX_DISKFULL_MESSAGES)
+                        System.err.println("Error writing the router log - disk full? " + ioe);
+                }
             }
         } catch (Throwable t) {
             t.printStackTrace();
diff --git a/android/src/net/i2p/util/SecureDirectory.java b/android/src/net/i2p/util/SecureDirectory.java
new file mode 100644
index 0000000000000000000000000000000000000000..0c34c91c054caf237279f2d3099a758cfca03c67
--- /dev/null
+++ b/android/src/net/i2p/util/SecureDirectory.java
@@ -0,0 +1,22 @@
+package net.i2p.util;
+
+import java.io.File;
+
+/**
+ *  setXXX() not available until API level 9 (Platform Version 2.3)
+ *  @since 0.8.7
+ */
+public class SecureDirectory extends File {
+
+    public SecureDirectory(String pathname) {
+        super(pathname);
+    }
+
+    public SecureDirectory(String parent, String child) {
+        super(parent, child);
+    }
+
+    public SecureDirectory(File parent, String child) {
+        super(parent, child);
+    }
+}
diff --git a/android/src/net/i2p/util/SecureFile.java b/android/src/net/i2p/util/SecureFile.java
new file mode 100644
index 0000000000000000000000000000000000000000..e9362ef946873f6f6875eb0677611a90bd38ef52
--- /dev/null
+++ b/android/src/net/i2p/util/SecureFile.java
@@ -0,0 +1,22 @@
+package net.i2p.util;
+
+import java.io.File;
+
+/**
+ *  setXXX() not available until API level 9 (Platform Version 2.3)
+ *  @since 0.8.7
+ */
+public class SecureFile extends SecureDirectory {
+
+    public SecureFile(String pathname) {
+        super(pathname);
+    }
+
+    public SecureFile(String parent, String child) {
+        super(parent, child);
+    }
+
+    public SecureFile(File parent, String child) {
+        super(parent, child);
+    }
+}
diff --git a/android/src/net/i2p/util/SecureFileOutputStream.java b/android/src/net/i2p/util/SecureFileOutputStream.java
new file mode 100644
index 0000000000000000000000000000000000000000..e45798cf988741fb430fae2f5cf334d4989354b6
--- /dev/null
+++ b/android/src/net/i2p/util/SecureFileOutputStream.java
@@ -0,0 +1,53 @@
+package net.i2p.util;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+
+import net.i2p.I2PAppContext;
+
+/**
+ *  setXXX() not available until API level 9 (Platform Version 2.3)
+ *  @since 0.8.7
+ */
+public class SecureFileOutputStream extends FileOutputStream {
+
+    /**
+     *  super()
+     */
+    public SecureFileOutputStream(String file) throws FileNotFoundException {
+        super(file);
+    }
+
+    /**
+     *  super()
+     */
+    public SecureFileOutputStream(String file, boolean append) throws FileNotFoundException {
+        super(file, append);
+    }
+
+    /**
+     *  super()
+     */
+    public SecureFileOutputStream(File file) throws FileNotFoundException {
+        super(file);
+    }
+
+    /**
+     *  super()
+     */
+    public SecureFileOutputStream(File file, boolean append) throws FileNotFoundException {
+        super(file, append);
+    }
+
+    /** @return false */
+    static boolean canSetPerms() {
+        return false;
+    }
+
+    /**
+     *  noop
+     */
+    public static void setPerms(File f) {
+    }
+}
diff --git a/apps/i2psnark/java/src/org/klomp/snark/Storage.java b/apps/i2psnark/java/src/org/klomp/snark/Storage.java
index 9c8f02cc4b25f477893253717406782fd830ed13..30118e9585b055e778576bbc13802e530ef41761 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/Storage.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/Storage.java
@@ -557,7 +557,8 @@ public class Storage
   private static final char[] ILLEGAL = new char[] {
         '<', '>', ':', '"', '/', '\\', '|', '?', '*',
         0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
-        16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31 };
+        16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+        0x7f };
 
   /**
    * Removes 'suspicious' characters from the given file name.
diff --git a/core/java/src/net/i2p/I2PAppContext.java b/core/java/src/net/i2p/I2PAppContext.java
index b54ca796998e5ba6f55de887e7935af9496326a7..fa352ada6acd6283dc3e1a838c7af5bf567bc1c2 100644
--- a/core/java/src/net/i2p/I2PAppContext.java
+++ b/core/java/src/net/i2p/I2PAppContext.java
@@ -687,14 +687,14 @@ public class I2PAppContext {
         }
     }
 
-    /** @deprecated unused */
+    /** @deprecated used only by syndie */
     public HMAC256Generator hmac256() {
         if (!_hmac256Initialized)
             initializeHMAC256();
         return _hmac256;
     }
 
-    /** @deprecated unused */
+    /** @deprecated used only by syndie */
     private void initializeHMAC256() {
         synchronized (this) {
             if (_hmac256 == null) {
diff --git a/core/java/src/net/i2p/crypto/ElGamalAESEngine.java b/core/java/src/net/i2p/crypto/ElGamalAESEngine.java
index df4867574d7f52911025297a414592f2eec845bf..4e6e634ca53dc5aee5f075a1859e31473c7070d5 100644
--- a/core/java/src/net/i2p/crypto/ElGamalAESEngine.java
+++ b/core/java/src/net/i2p/crypto/ElGamalAESEngine.java
@@ -91,14 +91,12 @@ public class ElGamalAESEngine {
         SessionTag st = new SessionTag(tag);
         SessionKey key = keyManager.consumeTag(st);
         SessionKey foundKey = new SessionKey();
-        foundKey.setData(null);
         SessionKey usedKey = new SessionKey();
         Set foundTags = new HashSet();
         byte decrypted[] = null;
         boolean wasExisting = false;
         if (key != null) {
             //if (_log.shouldLog(Log.DEBUG)) _log.debug("Key is known for tag " + st);
-            usedKey.setData(key.getData());
             long id = _context.random().nextLong();
             if (_log.shouldLog(Log.DEBUG))
                 _log.debug(id + ": Decrypting existing session encrypted with tag: " + st.toString() + ": key: " + key.toBase64() + ": " + data.length + " bytes: " + Base64.encode(data, 0, 64));
@@ -138,7 +136,7 @@ public class ElGamalAESEngine {
                 if (_log.shouldLog(Log.DEBUG)) 
                     _log.debug("Found key: " + foundKey.toBase64() + " tags: " + foundTags + " wasExisting? " + wasExisting);
                 keyManager.tagsReceived(foundKey, foundTags);
-            } else {
+            } else if (usedKey.getData() != null) {
                 if (_log.shouldLog(Log.DEBUG)) 
                     _log.debug("Used key: " + usedKey.toBase64() + " tags: " + foundTags + " wasExisting? " + wasExisting);
                 keyManager.tagsReceived(usedKey, foundTags);
@@ -160,11 +158,12 @@ public class ElGamalAESEngine {
      * the decryptAESBlock method & structure.
      *
      * @param foundTags set which is filled with any sessionTags found during decryption
-     * @param foundKey  session key which may be filled with a new sessionKey found during decryption
+     * @param foundKey  out parameter. Data must be unset when called; may be filled with a new sessionKey found during decryption
+     * @param usedKey out parameter. Data must be unset when called; usedKey.setData() will be called by this method on success.
      *
      * @return null if decryption fails
      */
-    byte[] decryptNewSession(byte data[], PrivateKey targetPrivateKey, Set foundTags, SessionKey usedKey,
+    private byte[] decryptNewSession(byte data[], PrivateKey targetPrivateKey, Set foundTags, SessionKey usedKey,
                                     SessionKey foundKey) throws DataFormatException {
         if (data == null) {
             //if (_log.shouldLog(Log.WARN)) _log.warn("Data is null, unable to decrypt new session");
@@ -231,19 +230,19 @@ public class ElGamalAESEngine {
      * If anything doesn't match up in decryption, it falls back to decryptNewSession
      *
      * @param foundTags set which is filled with any sessionTags found during decryption
-     * @param foundKey  session key which may be filled with a new sessionKey found during decryption
+     * @param foundKey  out parameter. Data must be unset when called; may be filled with a new sessionKey found during decryption
+     * @param usedKey out parameter. Data must be unset when called; usedKey.setData() will be called by this method on success.
+     *
      * @return decrypted data or null on failure
      *
      */
-    byte[] decryptExistingSession(byte data[], SessionKey key, PrivateKey targetPrivateKey, Set foundTags,
+    private byte[] decryptExistingSession(byte data[], SessionKey key, PrivateKey targetPrivateKey, Set foundTags,
                                          SessionKey usedKey, SessionKey foundKey) throws DataFormatException {
         byte preIV[] = new byte[32];
         System.arraycopy(data, 0, preIV, 0, preIV.length);
         Hash ivHash = _context.sha().calculateHash(preIV);
         byte iv[] = new byte[16];
         System.arraycopy(ivHash.getData(), 0, iv, 0, 16);
-        
-        usedKey.setData(key.getData());
 
         //_log.debug("Pre IV for decryptExistingSession: " + DataHelper.toString(preIV, 32));
         //_log.debug("SessionKey for decryptNewSession: " + DataHelper.toString(key.getData(), 32));
@@ -267,6 +266,7 @@ public class ElGamalAESEngine {
         //if (_log.shouldLog(Log.DEBUG))
         //    _log.debug("Decrypt with an EXISTING session tag successfull, # tags read: " + foundTags.size(),
         //               new Exception("Decrypted by"));
+        usedKey.setData(key.getData());
         return decrypted;
     }
 
@@ -287,14 +287,15 @@ public class ElGamalAESEngine {
      * consume it, but if it is null, record the keys, etc as part of a new session.
      *
      * @param foundTags set which is filled with any sessionTags found during decryption
-     * @param foundKey  session key which may be filled with a new sessionKey found during decryption
+     * @param foundKey  out parameter. Data must be unset when called; may be filled with a new sessionKey found during decryption
      * @return decrypted data or null on failure
      */
-    byte[] decryptAESBlock(byte encrypted[], SessionKey key, byte iv[], 
+    private byte[] decryptAESBlock(byte encrypted[], SessionKey key, byte iv[], 
                            byte sentTag[], Set foundTags, SessionKey foundKey) throws DataFormatException {
         return decryptAESBlock(encrypted, 0, encrypted.length, key, iv, sentTag, foundTags, foundKey);
     }
-    byte[] decryptAESBlock(byte encrypted[], int offset, int encryptedLen, SessionKey key, byte iv[], 
+
+    private byte[] decryptAESBlock(byte encrypted[], int offset, int encryptedLen, SessionKey key, byte iv[], 
                            byte sentTag[], Set foundTags, 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));
@@ -448,7 +449,7 @@ public class ElGamalAESEngine {
      * </pre>
      *
      */
-    byte[] encryptNewSession(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery,
+    private byte[] encryptNewSession(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery,
                                     SessionKey newKey, long paddedSize) {
         //_log.debug("Encrypting to a NEW session");
         byte elgSrcData[] = new byte[SessionKey.KEYSIZE_BYTES+32+158];
@@ -511,7 +512,7 @@ public class ElGamalAESEngine {
      * </pre>
      *
      */
-    byte[] encryptExistingSession(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery,
+    private byte[] encryptExistingSession(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery,
                                          SessionTag currentTag, SessionKey newKey, long paddedSize) {
         //_log.debug("Encrypting to an EXISTING session");
         byte rawTag[] = currentTag.getData();
@@ -543,11 +544,12 @@ public class ElGamalAESEngine {
      * </pre>
      *
      */
-    final byte[] encryptAESBlock(byte data[], SessionKey key, byte[] iv, Set tagsForDelivery, SessionKey newKey,
+    private final byte[] encryptAESBlock(byte data[], SessionKey key, byte[] iv, Set tagsForDelivery, SessionKey newKey,
                                         long paddedSize) {
         return encryptAESBlock(data, key, iv, tagsForDelivery, newKey, paddedSize, 0);
     }
-    final byte[] encryptAESBlock(byte data[], SessionKey key, byte[] iv, Set tagsForDelivery, SessionKey newKey,
+
+    private final byte[] encryptAESBlock(byte data[], SessionKey key, byte[] iv, Set tagsForDelivery, SessionKey newKey,
                                         long paddedSize, int prefixBytes) {
         //_log.debug("iv for encryption: " + DataHelper.toString(iv, 16));
         //_log.debug("Encrypting AES");
@@ -616,6 +618,7 @@ public class ElGamalAESEngine {
         context.random().nextBytes(rv);
         return rv;
     }
+
     final static int getPaddingSize(int curSize, long minPaddedSize) {
         int diff = 0;
         if (curSize < minPaddedSize) {
diff --git a/core/java/src/net/i2p/crypto/HMAC256Generator.java b/core/java/src/net/i2p/crypto/HMAC256Generator.java
index 3fc554639f5c6c6323cf6d303cd95fe3b4002e44..a4f541e34214ee1d4fd18b33d7f084e10d02134a 100644
--- a/core/java/src/net/i2p/crypto/HMAC256Generator.java
+++ b/core/java/src/net/i2p/crypto/HMAC256Generator.java
@@ -13,9 +13,12 @@ import org.bouncycastle.crypto.macs.I2PHMac;
 /**
  * Calculate the HMAC-SHA256 of a key+message.  All the good stuff occurs
  * in {@link org.bouncycastle.crypto.macs.I2PHMac} and 
- * {@link org.bouncycastle.crypto.digests.MD5Digest}.
+ * {@link net.i2p.crypto.Sha256Standalone}.
  *
- * deprecated unused
+ * This should be compatible with javax.crypto.Mac.getInstance("HmacSHA256")
+ * but that is untested.
+ *
+ * deprecated used only by syndie
  */
 public class HMAC256Generator extends HMACGenerator {
     public HMAC256Generator(I2PAppContext context) { super(context); }
diff --git a/core/java/src/net/i2p/crypto/HMACGenerator.java b/core/java/src/net/i2p/crypto/HMACGenerator.java
index 237c650550306ef5be52b57c64746d592202d203..3fe36c61e2c09eb89be4073817a3f440fa4e0569 100644
--- a/core/java/src/net/i2p/crypto/HMACGenerator.java
+++ b/core/java/src/net/i2p/crypto/HMACGenerator.java
@@ -3,41 +3,67 @@ package net.i2p.crypto;
 import java.util.Arrays;
 import java.util.concurrent.LinkedBlockingQueue;
 
+// following are for main() tests
+//import java.security.InvalidKeyException;
+//import java.security.Key;
+//import java.security.NoSuchAlgorithmException;
+//import javax.crypto.spec.SecretKeySpec;
+//import net.i2p.data.Base64;
+
 import net.i2p.I2PAppContext;
 import net.i2p.data.DataHelper;
 import net.i2p.data.Hash;
 import net.i2p.data.SessionKey;
+import net.i2p.util.SimpleByteCache;
 
 import org.bouncycastle.crypto.digests.MD5Digest;
 import org.bouncycastle.crypto.Mac;
 import org.bouncycastle.crypto.macs.I2PHMac;
 
 /**
- * Calculate the HMAC-MD5 of a key+message.  All the good stuff occurs
+ * Calculate the HMAC-MD5-128 of a key+message.  All the good stuff occurs
  * in {@link org.bouncycastle.crypto.macs.I2PHMac} and 
  * {@link org.bouncycastle.crypto.digests.MD5Digest}.
  *
+ * Keys are always 32 bytes.
+ * This is used only by UDP.
+ * Use deprecated outside the router, this may move to router.jar.
+ *
+ * NOTE THIS IS NOT COMPATIBLE with javax.crypto.Mac.getInstance("HmacMD5")
+ * as we tell I2PHMac that the digest length is 32 bytes, so it generates
+ * a different result.
+ *
+ * Quote jrandom:
+ * "The HMAC is hardcoded to use SHA256 digest size
+ * for backwards compatability.  next time we have a backwards
+ * incompatible change, we should update this."
+ *
+ * Does this mean he intended it to be compatible with MD5?
+ * See also 2005-07-05 status notes.
+ *
  */
 public class HMACGenerator {
-    private I2PAppContext _context;
     /** set of available HMAC instances for calculate */
     protected final LinkedBlockingQueue<I2PHMac> _available;
-    /** set of available byte[] buffers for verify */
-    private final LinkedBlockingQueue<byte[]> _availableTmp;
     
+    /**
+     *  @param context unused
+     */
     public HMACGenerator(I2PAppContext context) {
-        _context = context;
         _available = new LinkedBlockingQueue(32);
-        _availableTmp = new LinkedBlockingQueue(32);
     }
     
     /**
      * Calculate the HMAC of the data with the given key
+     *
+     * @return the first 16 bytes contain the HMAC, the last 16 bytes are zero
+     * @deprecated unused
      */
     public Hash calculate(SessionKey key, byte data[]) {
         if ((key == null) || (key.getData() == null) || (data == null))
             throw new NullPointerException("Null arguments for HMAC");
-        byte rv[] = new byte[Hash.HASH_LENGTH];
+        byte rv[] = acquireTmp();
+        Arrays.fill(rv, (byte)0x0);
         calculate(key, data, 0, data.length, rv, 0);
         return new Hash(rv);
     }
@@ -52,10 +78,8 @@ public class HMACGenerator {
         I2PHMac 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);
     }
     
     /**
@@ -77,7 +101,6 @@ public class HMACGenerator {
         mac.init(key.getData());
         mac.update(curData, curOffset, curLength);
         byte rv[] = acquireTmp();
-        //byte rv[] = new byte[Hash.HASH_LENGTH];
         mac.doFinal(rv, 0);
         release(mac);
         
@@ -93,6 +116,7 @@ public class HMACGenerator {
         // the HMAC is hardcoded to use SHA256 digest size
         // for backwards compatability.  next time we have a backwards
         // incompatible change, we should update this by removing ", 32"
+        // SEE NOTES ABOVE
         return new I2PHMac(new MD5Digest(), 32);
     }
 
@@ -100,17 +124,74 @@ public class HMACGenerator {
         _available.offer(mac);
     }
 
-    // temp buffers for verify(..)
+    /**
+     * Not really tmp, just from the byte array cache.
+     * Does NOT zero.
+     */
     private byte[] acquireTmp() {
-        byte rv[] = _availableTmp.poll();
-        if (rv != null)
-            Arrays.fill(rv, (byte)0x0);
-        else
-            rv = new byte[Hash.HASH_LENGTH];
+        byte rv[] = SimpleByteCache.acquire(Hash.HASH_LENGTH);
         return rv;
     }
 
     private void releaseTmp(byte tmp[]) {
-        _availableTmp.offer(tmp);
+        SimpleByteCache.release(tmp);
+    }
+
+    //private static final int RUNS = 100000;
+
+    /**
+     *  Test the BC and the JVM's implementations for speed
+     */
+/****  All this did was prove that we aren't compatible with standard HmacMD5
+    public static void main(String args[]) {
+        if (args.length != 2) {
+            System.err.println("Usage: HMACGenerator keySeedString dataString");
+            return;
+        }
+
+        byte[] rand = SHA256Generator.getInstance().calculateHash(args[0].getBytes()).getData();
+        byte[] data = args[1].getBytes();
+        Key keyObj = new SecretKeySpec(rand, "HmacMD5");
+
+        byte[] keyBytes = keyObj.getEncoded();
+        System.out.println("key bytes (" + keyBytes.length + ") is [" + Base64.encode(keyBytes) + "]");
+        SessionKey key = new SessionKey(keyBytes);
+        System.out.println("session key is [" + key);
+        System.out.println("key object is [" + keyObj);
+
+        HMACGenerator gen = new HMACGenerator(I2PAppContext.getGlobalContext());
+        byte[] result = new byte[16];
+        long start = System.currentTimeMillis();
+        for (int i = 0; i < RUNS; i++) {
+            gen.calculate(key, data, 0, data.length, result, 0);
+            if (i == 0)
+                System.out.println("MAC [" + Base64.encode(result) + "]");
+        }
+        long time = System.currentTimeMillis() - start;
+        System.out.println("Time for " + RUNS + " HMAC-MD5 computations:");
+        System.out.println("BC time (ms): " + time);
+
+        start = System.currentTimeMillis();
+        javax.crypto.Mac mac;
+        try {
+            mac = javax.crypto.Mac.getInstance("HmacMD5");
+        } catch (NoSuchAlgorithmException e) {
+            System.err.println("Fatal: " + e);
+            return;
+        }
+        for (int i = 0; i < RUNS; i++) {
+            try {
+                mac.init(keyObj);
+            } catch (InvalidKeyException e) {
+                System.err.println("Fatal: " + e);
+            }
+            byte[] sha = mac.doFinal(data);
+            if (i == 0)
+                System.out.println("MAC [" + Base64.encode(sha) + "]");
+        }
+        time = System.currentTimeMillis() - start;
+
+        System.out.println("JVM time (ms): " + time);
     }
+****/
 }
diff --git a/core/java/src/net/i2p/crypto/KeyGenerator.java b/core/java/src/net/i2p/crypto/KeyGenerator.java
index 7853063f32b66ce940da09fc15191d437fcbe322..689516be059a3b9fef5f4ec6f3abaa0bb0bc30ef 100644
--- a/core/java/src/net/i2p/crypto/KeyGenerator.java
+++ b/core/java/src/net/i2p/crypto/KeyGenerator.java
@@ -20,6 +20,7 @@ import net.i2p.data.SessionKey;
 import net.i2p.data.Signature;
 import net.i2p.data.SigningPrivateKey;
 import net.i2p.data.SigningPublicKey;
+import net.i2p.data.SimpleDataStructure;
 import net.i2p.util.Clock;
 import net.i2p.util.Log;
 import net.i2p.util.NativeBigInteger;
@@ -29,18 +30,17 @@ import net.i2p.util.RandomSource;
  * @author jrandom
  */
 public class KeyGenerator {
-    private Log _log;
-    private I2PAppContext _context;
+    private final Log _log;
+    private final I2PAppContext _context;
 
     public KeyGenerator(I2PAppContext context) {
         _log = context.logManager().getLog(KeyGenerator.class);
         _context = context;
     }
+
     public static KeyGenerator getInstance() {
         return I2PAppContext.getGlobalContext().keyGenerator();
     }
-    
-
 
     /** Generate a private 256 bit session key
      * @return session key
@@ -84,11 +84,11 @@ public class KeyGenerator {
      * index 1 is a PrivateKey
      * @return pair of keys
      */
-    public Object[] generatePKIKeypair() {
+    public SimpleDataStructure[] generatePKIKeypair() {
         BigInteger a = new NativeBigInteger(PUBKEY_EXPONENT_SIZE, _context.random());
         BigInteger aalpha = CryptoConstants.elgg.modPow(a, CryptoConstants.elgp);
 
-        Object[] keys = new Object[2];
+        SimpleDataStructure[] keys = new SimpleDataStructure[2];
         keys[0] = new PublicKey();
         keys[1] = new PrivateKey();
         byte[] k0 = aalpha.toByteArray();
@@ -97,8 +97,8 @@ public class KeyGenerator {
         // bigInteger.toByteArray returns SIGNED integers, but since they'return positive,
         // signed two's complement is the same as unsigned
 
-        ((PublicKey) keys[0]).setData(padBuffer(k0, PublicKey.KEYSIZE_BYTES));
-        ((PrivateKey) keys[1]).setData(padBuffer(k1, PrivateKey.KEYSIZE_BYTES));
+        keys[0].setData(padBuffer(k0, PublicKey.KEYSIZE_BYTES));
+        keys[1].setData(padBuffer(k1, PrivateKey.KEYSIZE_BYTES));
 
         return keys;
     }
@@ -120,8 +120,8 @@ public class KeyGenerator {
      * index 1 is a SigningPrivateKey
      * @return pair of keys
      */
-    public Object[] generateSigningKeypair() {
-        Object[] keys = new Object[2];
+    public SimpleDataStructure[] generateSigningKeypair() {
+        SimpleDataStructure[] keys = new SimpleDataStructure[2];
         BigInteger x = null;
 
         // make sure the random key is less than the DSA q
@@ -135,8 +135,8 @@ public class KeyGenerator {
         byte k0[] = padBuffer(y.toByteArray(), SigningPublicKey.KEYSIZE_BYTES);
         byte k1[] = padBuffer(x.toByteArray(), SigningPrivateKey.KEYSIZE_BYTES);
 
-        ((SigningPublicKey) keys[0]).setData(k0);
-        ((SigningPrivateKey) keys[1]).setData(k1);
+        keys[0].setData(k0);
+        keys[1].setData(k1);
         return keys;
     }
 
diff --git a/core/java/src/net/i2p/crypto/SHA256Generator.java b/core/java/src/net/i2p/crypto/SHA256Generator.java
index a62f2129b87db3c109f0452221e7dadba31a7677..4224d2e2194138b0145244320e91ef70ab1e8667 100644
--- a/core/java/src/net/i2p/crypto/SHA256Generator.java
+++ b/core/java/src/net/i2p/crypto/SHA256Generator.java
@@ -32,6 +32,9 @@ public final class SHA256Generator {
         _useGnu = useGnu;
     }
 
+    /**
+     *  @param context unused
+     */
     public SHA256Generator(I2PAppContext context) {
         _digests = new LinkedBlockingQueue(32);
     }
diff --git a/core/java/src/net/i2p/data/SessionKey.java b/core/java/src/net/i2p/data/SessionKey.java
index 7621c2b8a7cdad0258464ddac9d00880dc30fdc0..bbe1e533c55ca71fd4ab8a3e148b1e332be0ad36 100644
--- a/core/java/src/net/i2p/data/SessionKey.java
+++ b/core/java/src/net/i2p/data/SessionKey.java
@@ -41,7 +41,7 @@ public class SessionKey extends SimpleDataStructure {
      */
     @Override
     public void setData(byte[] data) {
-        _data = data;
+        super.setData(data);
         _preparedKey = null;
     }
     
diff --git a/core/java/src/net/i2p/util/ReusableGZIPOutputStream.java b/core/java/src/net/i2p/util/ReusableGZIPOutputStream.java
index bbc1c334a41a60c56818a48e35f2302cf9438631..f7665e2fc2b48970c3de68a1e4b80bbb558af32a 100644
--- a/core/java/src/net/i2p/util/ReusableGZIPOutputStream.java
+++ b/core/java/src/net/i2p/util/ReusableGZIPOutputStream.java
@@ -15,7 +15,9 @@ import net.i2p.data.DataHelper;
  */
 public class ReusableGZIPOutputStream extends ResettableGZIPOutputStream {
     // Apache Harmony 5.0M13 Deflater doesn't work after reset()
-    private static final boolean ENABLE_CACHING = !System.getProperty("java.vendor").startsWith("Apache");
+    // Neither does Android
+    private static final boolean ENABLE_CACHING = !(System.getProperty("java.vendor").startsWith("Apache") ||
+                                                    System.getProperty("java.vendor").contains("Android"));
     private static final LinkedBlockingQueue<ReusableGZIPOutputStream> _available;
     static {
         if (ENABLE_CACHING)
diff --git a/core/java/src/org/bouncycastle/crypto/macs/I2PHMac.java b/core/java/src/org/bouncycastle/crypto/macs/I2PHMac.java
index 125583613589391c50b042b8fc489d5762f9a790..b8d9c073ee818870ee9a6b440827753a24aff4f3 100644
--- a/core/java/src/org/bouncycastle/crypto/macs/I2PHMac.java
+++ b/core/java/src/org/bouncycastle/crypto/macs/I2PHMac.java
@@ -27,9 +27,10 @@ package org.bouncycastle.crypto.macs;
  */
 
 //import org.bouncycastle.crypto.CipherParameters;
-import java.util.ArrayList;
 import java.util.Arrays;
 
+import net.i2p.util.SimpleByteCache;
+
 import org.bouncycastle.crypto.Digest;
 import org.bouncycastle.crypto.Mac;
 
@@ -65,6 +66,11 @@ implements Mac
     {
         this(digest, digest.getDigestSize()); 
     }
+
+    /**
+     *  @param sz override the digest's size
+     *  SEE NOTES in HMACGenerator about why this isn't compatible with standard HmacMD5
+     */
     public I2PHMac(
         Digest digest, int sz)
     {
@@ -165,28 +171,14 @@ implements Mac
         return len;
     }
     
-    /**
-     * list of buffers - index 0 is the cache for 32 byte arrays, while index 1 is the cache for 16 byte arrays
-     */
-    private static ArrayList _tmpBuf[] = new ArrayList[] { new ArrayList(), new ArrayList() };
     private static byte[] acquireTmp(int sz) {
-        byte rv[] = null;
-        synchronized (_tmpBuf[sz == 32 ? 0 : 1]) {
-            if (!_tmpBuf[sz == 32 ? 0 : 1].isEmpty())
-                rv = (byte[])_tmpBuf[sz == 32 ? 0 : 1].remove(0);
-        }
-        if (rv != null)
-            Arrays.fill(rv, (byte)0x0);
-        else
-            rv = new byte[sz];
+        byte[] rv = SimpleByteCache.acquire(sz);
+        Arrays.fill(rv, (byte)0x0);
         return rv;
     }
+
     private static void releaseTmp(byte buf[]) {
-        if (buf == null) return;
-        synchronized (_tmpBuf[buf.length == 32 ? 0 : 1]) {
-            if (_tmpBuf[buf.length == 32 ? 0 : 1].size() < 100) 
-                _tmpBuf[buf.length == 32 ? 0 : 1].add((Object)buf);
-        }
+        SimpleByteCache.release(buf);
     }
 
     /**
diff --git a/history.txt b/history.txt
index 53958417ef6d8a560417a5f7e8e938660a07c9ae..2260f8b26e86f7388124ee35db37ade617f3a49a 100644
--- a/history.txt
+++ b/history.txt
@@ -1,3 +1,12 @@
+2011-06-02 zzz
+    * Android: Build fixes
+    * Crypto:
+      - HMAC Javadocs and cleanups
+      - HMAC Use SimpleByteCache
+    * ElGamalAESEngine: Fixups required after SessionKey enforcement
+    * Reseed: Give up on a seed after 90% of fetches fail
+    * SessionKey: Enforce data size and prevent reuse
+
 2011-06-02 sponge
     * Remove txt file in BOB.jar as per zzz's request.
 
diff --git a/router/java/src/net/i2p/data/i2np/DatabaseStoreMessage.java b/router/java/src/net/i2p/data/i2np/DatabaseStoreMessage.java
index b0a4cfc6d1ddbfdf9b781f74ff3f9493e35d6b97..6ac4b43673796dfa54083b45b13de47a6c885bda 100644
--- a/router/java/src/net/i2p/data/i2np/DatabaseStoreMessage.java
+++ b/router/java/src/net/i2p/data/i2np/DatabaseStoreMessage.java
@@ -150,12 +150,15 @@ public class DatabaseStoreMessage extends I2NPMessageImpl {
         int len = Hash.HASH_LENGTH + 1 + 4; // key+type+replyToken
         if (_replyToken > 0) 
             len += 4 + Hash.HASH_LENGTH; // replyTunnel+replyGateway
-        if (_dbEntry.getType() == DatabaseEntry.KEY_TYPE_LEASESET) {
+        int type = _dbEntry.getType();
+        if (type == DatabaseEntry.KEY_TYPE_LEASESET) {
             _byteCache = _dbEntry.toByteArray();
-        } else if (_dbEntry.getType() == DatabaseEntry.KEY_TYPE_ROUTERINFO) {
+        } else if (type == DatabaseEntry.KEY_TYPE_ROUTERINFO) {
             byte uncompressed[] = _dbEntry.toByteArray();
             _byteCache = DataHelper.compress(uncompressed);
             len += 2;
+        } else {
+            throw new IllegalStateException("Invalid key type " + type);
         }
         len += _byteCache.length;
         return len;
@@ -166,7 +169,7 @@ public class DatabaseStoreMessage extends I2NPMessageImpl {
         if (_dbEntry == null) throw new I2NPMessageException("Missing entry");
         int type = _dbEntry.getType();
         if (type != DatabaseEntry.KEY_TYPE_LEASESET && type != DatabaseEntry.KEY_TYPE_ROUTERINFO)
-            throw new I2NPMessageException("Invalid key type");
+            throw new I2NPMessageException("Invalid key type " + type);
         
         // Use the hash of the DatabaseEntry
         System.arraycopy(getKey().getData(), 0, out, curIndex, Hash.HASH_LENGTH);
diff --git a/router/java/src/net/i2p/router/Router.java b/router/java/src/net/i2p/router/Router.java
index f9275bada05932564a0fd36b6bd1946f3aa953a1..1245a6208605d2b497d70e4fec0f511d24c78cb2 100644
--- a/router/java/src/net/i2p/router/Router.java
+++ b/router/java/src/net/i2p/router/Router.java
@@ -311,8 +311,11 @@ public class Router {
     }
     
     public RouterInfo getRouterInfo() { return _routerInfo; }
+
     public void setRouterInfo(RouterInfo info) { 
         _routerInfo = info; 
+        if (_log.shouldLog(Log.INFO))
+            _log.info("setRouterInfo() : " + info, new Exception("I did it"));
         if (info != null)
             _context.jobQueue().addJob(new PersistRouterInfoJob(_context));
     }
@@ -614,6 +617,10 @@ public class Router {
             }
         }
         // hard and ugly
+        if (System.getProperty("wrapper.version") != null)
+            _log.log(Log.CRIT, "Restarting with new router identity");
+        else
+            _log.log(Log.CRIT, "Shutting down because old router identity was invalid - restart I2P");
         finalShutdown(EXIT_HARD_RESTART);
     }
     
diff --git a/router/java/src/net/i2p/router/RouterContext.java b/router/java/src/net/i2p/router/RouterContext.java
index 4ac21fce55f35f924779be56bfac6d662d53e22c..c7d54f88d21d60eb1b5541d5ef81b581f39c7226 100644
--- a/router/java/src/net/i2p/router/RouterContext.java
+++ b/router/java/src/net/i2p/router/RouterContext.java
@@ -121,7 +121,7 @@ public class RouterContext extends I2PAppContext {
 
     public void initAll() {
         if (getBooleanProperty("i2p.dummyClientFacade"))
-            System.err.println("i2p.dummpClientFacade currently unsupported");
+            System.err.println("i2p.dummyClientFacade currently unsupported");
         _clientManagerFacade = new ClientManagerFacadeImpl(this);
         // removed since it doesn't implement InternalClientManager for now
         //else
diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java
index ea17ad55f3270be34b24fd56bd0b1495da601896..e1f87e077ded8c1235896b49923811522eadbbfe 100644
--- a/router/java/src/net/i2p/router/RouterVersion.java
+++ b/router/java/src/net/i2p/router/RouterVersion.java
@@ -18,7 +18,7 @@ public class RouterVersion {
     /** deprecated */
     public final static String ID = "Monotone";
     public final static String VERSION = CoreVersion.VERSION;
-    public final static long BUILD = 15;
+    public final static long BUILD = 16;
 
     /** for example "-test" */
     public final static String EXTRA = "";
diff --git a/router/java/src/net/i2p/router/networkdb/PublishLocalRouterInfoJob.java b/router/java/src/net/i2p/router/networkdb/PublishLocalRouterInfoJob.java
index c2275a90d7197cb98bf4ea062749ce8c50fdf15f..8ca29317c3abcca0f504f10649d38f6b9ca3c465 100644
--- a/router/java/src/net/i2p/router/networkdb/PublishLocalRouterInfoJob.java
+++ b/router/java/src/net/i2p/router/networkdb/PublishLocalRouterInfoJob.java
@@ -65,7 +65,7 @@ public class PublishLocalRouterInfoJob extends JobImpl {
             try {
                 getContext().netDb().publish(ri);
             } catch (IllegalArgumentException iae) {
-                _log.log(Log.CRIT, "Error publishing our identity - corrupt?", iae);
+                _log.log(Log.CRIT, "Error publishing our identity - corrupt? Restart required", iae);
                 getContext().router().rebuildNewIdentity();
             }
         } catch (DataFormatException dfe) {
diff --git a/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java b/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java
index 3e1c0b0753c533f69ba5eff10d29cecb2c53647a..1fb70e36db6c0acc4945b8954e284252b8eda3b7 100644
--- a/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java
+++ b/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java
@@ -344,6 +344,9 @@ public class Reseeder {
                     } catch (IOException e) {
                         errors++;
                     }
+                    // Give up on this one after 10 with only 0 or 1 good
+                    if (errors >= 10 && fetched <= 1)
+                        break;
                 }
                 System.err.println("Reseed got " + fetched + " router infos from " + seedURL + " with " + errors + " errors");