diff --git a/INSTALL-headless.txt b/INSTALL-headless.txt
index 6f545758081bc7667ab5ee02f853e771fc2ff0b5..3ae8a14f07066d5ce95e485617b2bff2327ebf86 100644
--- a/INSTALL-headless.txt
+++ b/INSTALL-headless.txt
@@ -25,21 +25,22 @@ where there are comments labeled "PORTABLE". Do this before you
 run I2P for the first time.
 
 To start I2P:
-   (*nix): sh i2prouter start
+   (*nix, BSD, Mac): sh i2prouter start
    (win*): I2P.exe
-   (non-x86 platforms PPC, ARM, etc): sh runplain.sh
+   (platforms without wrapper support): sh runplain.sh
 
 To stop I2P (gracefully):
    lynx http://localhost:7657/summaryframe (click "Shutdown")
+   or (*nix, BSD, Mac) sh i2prouter graceful
 
 To stop I2P immediately:
-   sh i2prouter stop
+   (*nix, BSD, Mac) sh i2prouter stop
 
 To uninstall I2P:
    rm -rf $I2PInstallDir ~/.i2p
 
 Supported JVMs:
-  All platforms: Java 1.6 or higher required; 1.7 or higher recommended
+  All platforms: Java 1.7 or higher required
   Windows: OpenJDK or Oracle from http://java.com/download
   Linux:   OpenJDK or Oracle from http://java.com/download
   FreeBSD: OpenJDK or Oracle from http://java.com/download
diff --git a/INSTALL.txt b/INSTALL.txt
index 23701b0411fadd13db7390d39e4c90b86ef60135..d9960fc3a0e90b414b61dae102ee68265d6b7ba9 100644
--- a/INSTALL.txt
+++ b/INSTALL.txt
@@ -1,8 +1,9 @@
 I2P source installation instructions
 
 Prerequisites to build from source:
-	Java SDK (preferably Oracle/Sun or OpenJDK) 1.6.0 or higher
+	Java SDK (preferably Oracle/Sun or OpenJDK) 1.7.0 or higher
           Non-linux operating systems and JVMs: See https://trac.i2p2.de/wiki/java
+          Certain subsystems for embedded (core, router, mstreaming, streaming, i2ptunnel) require only Java 1.6
 	Apache Ant 1.7.0 or higher
 	The xgettext, msgfmt, and msgmerge tools installed
 	from the GNU gettext package http://www.gnu.org/software/gettext/
@@ -40,29 +41,30 @@ or on Windows, just double-click on i2pinstall.exe.
 Or move the i2pupdate.zip file into an existing installation directory and restart.
 
 To start I2P:
-   (*nix): sh i2prouter start
+   (*nix, BSD, Mac): sh i2prouter start
    (win*): I2P.exe or i2prouter.bat
-   (non-x86 platforms PPC, ARM, etc): sh runplain.sh
+   (platforms without wrapper support): sh runplain.sh
 
 To install I2P as a system service:
-   (*nix) sh i2prouter install
+   (*nix, BSD, Mac) sh i2prouter install
    (win*) install_i2p_service_winnt.bat
 
 To uninstall I2P as a system service:
-   (*nix) sh i2prouter remove
+   (*nix, BSD, Mac) sh i2prouter remove
    (win*) uninstall_i2p-service_winnt.bat
 
 To stop I2P (gracefully):
    lynx http://localhost:7657/summaryframe (click "Shutdown")
+   or (*nix, BSD, Mac) sh i2prouter graceful
 
 To stop I2P immediately:
-   sh i2prouter stop
+   (*nix, BSD, Mac) sh i2prouter stop
 
 To uninstall I2P:
    rm -rf $I2PInstallDir ~/.i2p
 
 Supported JVMs:
-  Windows: Latest available from http://java.com/download (1.5+ supported)
-  Linux:   Latest available from http://java.com/download (1.5+ supported)
-  FreeBSD: 1.5-compatible (NIO required)
+  Windows: Latest available from http://java.com/download (1.7+ supported)
+  Linux:   Latest available from http://java.com/download (1.7+ supported)
+  FreeBSD: 1.7-compatible (NIO required)
   Other operating systems and JVMs: See http://trac.i2p2.de/wiki/java
diff --git a/README.txt b/README.txt
index 0a99e21a49f8c3166560b2568cfcb94d0b57e020..b7a4b8e4053e2d8a1b6e1046a083e8e6bdc8b169 100644
--- a/README.txt
+++ b/README.txt
@@ -1,6 +1,7 @@
 Prerequisites to build from source:
-	Java SDK (preferably Oracle/Sun or OpenJDK) 1.6.0 or higher
+	Java SDK (preferably Oracle/Sun or OpenJDK) 1.7.0 or higher
           Non-linux operating systems and JVMs: See https://trac.i2p2.de/wiki/java
+          Certain subsystems for embedded (core, router, mstreaming, streaming, i2ptunnel) require only Java 1.6
 	Apache Ant 1.7.0 or higher
 	The xgettext, msgfmt, and msgmerge tools installed
 	  from the GNU gettext package http://www.gnu.org/software/gettext/
diff --git a/apps/BOB/src/net/i2p/BOB/BOB.java b/apps/BOB/src/net/i2p/BOB/BOB.java
index e1106c9bbb50619dfdb41bafe936519d4022a729..c8b06b5e954489409da6fcc9b21dcd18c6d68f58 100644
--- a/apps/BOB/src/net/i2p/BOB/BOB.java
+++ b/apps/BOB/src/net/i2p/BOB/BOB.java
@@ -247,11 +247,11 @@ public class BOB implements Runnable, ClientApp {
 			save = true;
 		}
 		if (!props.containsKey("inbound.length")) {
-			props.setProperty("inbound.length", "1");
+			props.setProperty("inbound.length", "3");
 			save = true;
 		}
 		if (!props.containsKey("outbound.length")) {
-			props.setProperty("outbound.length", "1");
+			props.setProperty("outbound.length", "3");
 			save = true;
 		}
 		if (!props.containsKey("inbound.lengthVariance")) {
diff --git a/apps/addressbook/java/src/net/i2p/addressbook/ConfigIterator.java b/apps/addressbook/java/src/net/i2p/addressbook/ConfigIterator.java
index 30c21d19bf269592b3e522066f2b0652e615b060..10119d844e5bac72ecc918740a68e52bac31a35e 100644
--- a/apps/addressbook/java/src/net/i2p/addressbook/ConfigIterator.java
+++ b/apps/addressbook/java/src/net/i2p/addressbook/ConfigIterator.java
@@ -32,6 +32,8 @@ import java.util.Locale;
 import java.util.Map;
 import java.util.NoSuchElementException;
 
+import net.i2p.data.DataHelper;
+
 /**
  *  A class to iterate through a hosts.txt or config file without
  *  reading the whole thing into memory.
@@ -69,7 +71,7 @@ class ConfigIterator implements Iterator<Map.Entry<String, String>>, Closeable {
             String inputLine = input.readLine();
             while (inputLine != null) {
                 inputLine = ConfigParser.stripComments(inputLine);
-                String[] splitLine = inputLine.split("=");
+                String[] splitLine = DataHelper.split(inputLine, "=");
                 if (splitLine.length == 2) {
                     next = new ConfigEntry(splitLine[0].trim().toLowerCase(Locale.US), splitLine[1].trim());
                     return true;
diff --git a/apps/addressbook/java/src/net/i2p/addressbook/ConfigParser.java b/apps/addressbook/java/src/net/i2p/addressbook/ConfigParser.java
index e2cd8ab2a0abaaff9575170ec41d149465303362..625b6e3d042e958a5219418c06a63610abe82ed2 100644
--- a/apps/addressbook/java/src/net/i2p/addressbook/ConfigParser.java
+++ b/apps/addressbook/java/src/net/i2p/addressbook/ConfigParser.java
@@ -35,6 +35,7 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 
+import net.i2p.data.DataHelper;
 import net.i2p.util.SecureFile;
 import net.i2p.util.SecureFileOutputStream;
 import net.i2p.util.SystemVersion;
@@ -93,7 +94,7 @@ class ConfigParser {
         inputLine = input.readLine();
         while (inputLine != null) {
             inputLine = stripComments(inputLine);
-            String[] splitLine = inputLine.split("=");
+            String[] splitLine = DataHelper.split(inputLine, "=");
             if (splitLine.length == 2) {
                 result.put(splitLine[0].trim().toLowerCase(Locale.US), splitLine[1].trim());
             }
diff --git a/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java b/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java
index 5e7d61ae988a12c619fa7c316b8d94b30fc50e72..54e9630edc634fa25d95220fd03c7890eeeada2c 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java
@@ -3,8 +3,8 @@ package org.klomp.snark;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.IOException;
-import java.net.MalformedURLException;
-import java.net.URL;
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -590,10 +590,10 @@ public class I2PSnarkUtil {
      */
     public boolean isKnownOpenTracker(String url) { 
         try {
-           URL u = new URL(url);
+           URI u = new URI(url);
            String host = u.getHost();
            return host != null && SnarkManager.KNOWN_OPENTRACKERS.contains(host);
-        } catch (MalformedURLException mue) {
+        } catch (URISyntaxException use) {
            return false;
         }
     }
diff --git a/apps/i2psnark/java/src/org/klomp/snark/Peer.java b/apps/i2psnark/java/src/org/klomp/snark/Peer.java
index cba955f691da30bf5f7721485a9488651f1911bf..fbbb1859af47a8a11d6a5f17fcd103e36a126814 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/Peer.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/Peer.java
@@ -62,7 +62,7 @@ public class Peer implements Comparable<Peer>
 
   // Keeps state for in/out connections.  Non-null when the handshake
   // was successful, the connection setup and runs
-  PeerState state;
+  volatile PeerState state;
 
   /** shared across all peers on this torrent */
   MagnetState magnetState;
diff --git a/apps/i2psnark/java/src/org/klomp/snark/PeerConnectionIn.java b/apps/i2psnark/java/src/org/klomp/snark/PeerConnectionIn.java
index 1f47b59b6056c00addf57a7d435e5dfef3fd923f..da1b943332f8e4c38664c9fd9dbe393deee89b85 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/PeerConnectionIn.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/PeerConnectionIn.java
@@ -39,7 +39,7 @@ class PeerConnectionIn implements Runnable
   private static final int MAX_MSG_SIZE = Math.max(PeerState.PARTSIZE + 9,
                                                    MagnetState.CHUNK_SIZE + 100);  // 100 for the ext msg dictionary
 
-  private Thread thread;
+  private volatile Thread thread;
   private volatile boolean quit;
 
   long lastRcvd;
@@ -75,9 +75,12 @@ class PeerConnectionIn implements Runnable
     thread = Thread.currentThread();
     try
       {
-        PeerState ps = peer.state;
-        while (!quit && ps != null)
+        while (!quit)
           {
+            final PeerState ps = peer.state;
+            if (ps == null)
+                break;
+
             // Common variables used for some messages.
             int piece;
             int begin;
@@ -91,9 +94,9 @@ class PeerConnectionIn implements Runnable
 
             if (i == 0)
               {
-                ps.keepAliveMessage();
                 if (_log.shouldLog(Log.DEBUG)) 
                     _log.debug("Received keepalive from " + peer);
+                ps.keepAliveMessage();
                 continue;
               }
             
@@ -101,51 +104,51 @@ class PeerConnectionIn implements Runnable
             switch (b)
               {
               case Message.CHOKE:
-                ps.chokeMessage(true);
                 if (_log.shouldLog(Log.DEBUG)) 
                     _log.debug("Received choke from " + peer);
+                ps.chokeMessage(true);
                 break;
 
               case Message.UNCHOKE:
-                ps.chokeMessage(false);
                 if (_log.shouldLog(Log.DEBUG)) 
                     _log.debug("Received unchoke from " + peer);
+                ps.chokeMessage(false);
                 break;
 
               case Message.INTERESTED:
-                ps.interestedMessage(true);
                 if (_log.shouldLog(Log.DEBUG)) 
                     _log.debug("Received interested from " + peer);
+                ps.interestedMessage(true);
                 break;
 
               case Message.UNINTERESTED:
-                ps.interestedMessage(false);
                 if (_log.shouldLog(Log.DEBUG)) 
                     _log.debug("Received not interested from " + peer);
+                ps.interestedMessage(false);
                 break;
 
               case Message.HAVE:
                 piece = din.readInt();
-                ps.haveMessage(piece);
                 if (_log.shouldLog(Log.DEBUG)) 
                     _log.debug("Received havePiece(" + piece + ") from " + peer);
+                ps.haveMessage(piece);
                 break;
 
               case Message.BITFIELD:
                 byte[] bitmap = new byte[i-1];
                 din.readFully(bitmap);
-                ps.bitfieldMessage(bitmap);
                 if (_log.shouldLog(Log.DEBUG)) 
                     _log.debug("Received bitmap from " + peer  + ": size=" + (i-1) /* + ": " + ps.bitfield */ );
+                ps.bitfieldMessage(bitmap);
                 break;
 
               case Message.REQUEST:
                 piece = din.readInt();
                 begin = din.readInt();
                 len = din.readInt();
-                ps.requestMessage(piece, begin, len);
                 if (_log.shouldLog(Log.DEBUG)) 
                     _log.debug("Received request(" + piece + "," + begin + ") from " + peer);
+                ps.requestMessage(piece, begin, len);
                 break;
 
               case Message.PIECE:
@@ -156,9 +159,9 @@ class PeerConnectionIn implements Runnable
                 if (req != null)
                   {
                     req.read(din);
-                    ps.pieceMessage(req);
                     if (_log.shouldLog(Log.DEBUG)) 
                         _log.debug("Received data(" + piece + "," + begin + ") from " + peer);
+                    ps.pieceMessage(req);
                   }
                 else
                   {
@@ -175,16 +178,16 @@ class PeerConnectionIn implements Runnable
                 piece = din.readInt();
                 begin = din.readInt();
                 len = din.readInt();
-                ps.cancelMessage(piece, begin, len);
                 if (_log.shouldLog(Log.DEBUG)) 
                     _log.debug("Received cancel(" + piece + "," + begin + ") from " + peer);
+                ps.cancelMessage(piece, begin, len);
                 break;
 
               case Message.PORT:
                 int port = din.readUnsignedShort();
-                ps.portMessage(port);
                 if (_log.shouldLog(Log.DEBUG)) 
                     _log.debug("Received port message from " + peer);
+                ps.portMessage(port);
                 break;
 
               case Message.EXTENSION:
@@ -247,11 +250,9 @@ class PeerConnectionIn implements Runnable
         if (_log.shouldLog(Log.INFO))
             _log.info("IOError talking with " + peer, ioe);
       }
-    catch (Throwable t)
+    catch (RuntimeException t)
       {
         _log.error("Error talking with " + peer, t);
-        if (t instanceof OutOfMemoryError)
-            throw (OutOfMemoryError)t;
       }
     finally
       {
diff --git a/apps/i2psnark/java/src/org/klomp/snark/Snark.java b/apps/i2psnark/java/src/org/klomp/snark/Snark.java
index f8dadb627fd51730a8ff3a71504b5f36bc21b707..b0f9833136b37f219e4dfcd7c7f95c9fdb850d98 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/Snark.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/Snark.java
@@ -911,6 +911,30 @@ public class Snark
         return -1;
     }
 
+    /**
+     *  Bytes not received and set to skipped.
+     *  This is not the same as the total of all skipped files,
+     *  since pieces may span multiple files.
+     *
+     *  @return exact value. or 0 if no storage yet.
+     *  @since 0.9.24
+     */
+    public long getSkippedLength() {
+        PeerCoordinator coord = coordinator;
+        if (coord != null) {
+            // fast way
+            long r = getRemainingLength();
+            if (r <= 0)
+                return 0;
+            long n = coord.getNeededLength();
+            return r - n;
+        } else if (storage != null) {
+            // slow way
+            return storage.getSkippedLength();
+        }
+        return 0;
+    }
+
     /**
      *  Does not account (i.e. includes) for skipped files.
      *  @return number of pieces still needed (magnet mode or not), or -1 if unknown
diff --git a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java
index 688cf7e95cb548cabfb3c621ae21a5a4305bf09c..f8996d14bd9e9f77058179cc49331f4c6dfd653e 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java
@@ -189,7 +189,7 @@ public class SnarkManager implements CompleteListener {
         for (int i = 1; i < DEFAULT_TRACKERS.length; i += 2) {
             if (DEFAULT_TRACKERS[i-1].equals("TheBland") && !SigType.ECDSA_SHA256_P256.isAvailable())
                 continue;
-            String urls[] = DEFAULT_TRACKERS[i].split("=", 2);
+            String urls[] = DataHelper.split(DEFAULT_TRACKERS[i], "=", 2);
             ann.add(urls[0]);
         }
         DEFAULT_TRACKER_ANNOUNCES = Collections.unmodifiableSet(ann);
@@ -1078,7 +1078,7 @@ public class SnarkManager implements CompleteListener {
             val = dflt;
         if (val == null)
             return Collections.emptyList();
-        return Arrays.asList(val.split(","));
+        return Arrays.asList(DataHelper.split(val, ","));
     }
 
     /**
@@ -1611,7 +1611,7 @@ public class SnarkManager implements CompleteListener {
             return;
         int filecount = metainfo.getFiles().size();
         int[] rv = new int[filecount];
-        String[] arr = pri.split(",");
+        String[] arr = DataHelper.split(pri, ",");
         for (int i = 0; i < filecount && i < arr.length; i++) {
             if (arr[i].length() > 0) {
                 try {
@@ -2051,7 +2051,7 @@ public class SnarkManager implements CompleteListener {
                     synchronized (_snarks) {
                         ok = monitorTorrents(dir);
                     }
-                } catch (Exception e) {
+                } catch (RuntimeException e) {
                     _log.error("Error in the DirectoryMonitor", e);
                     ok = false;
                 }
@@ -2060,7 +2060,7 @@ public class SnarkManager implements CompleteListener {
                     try {
                         addMagnets();
                         doMagnets = false;
-                    } catch (Exception e) {
+                    } catch (RuntimeException e) {
                         _log.error("Error in the DirectoryMonitor", e);
                     }
                     if (!_snarks.isEmpty())
@@ -2266,7 +2266,7 @@ public class SnarkManager implements CompleteListener {
                     // Snark.fatal() throws a RuntimeException
                     // don't let one bad torrent kill the whole loop
                     addTorrent(name, null, !shouldAutoStart());
-                } catch (Exception e) {
+                } catch (RuntimeException e) {
                     addMessage(_t("Error: Could not add the torrent {0}", name) + ": " + e);
                     _log.error("Unable to add the torrent " + name, e);
                     rv = false;
@@ -2285,7 +2285,7 @@ public class SnarkManager implements CompleteListener {
                     // Snark.fatal() throws a RuntimeException
                     // don't let one bad torrent kill the whole loop
                     stopTorrent(name, true);
-                } catch (Exception e) {
+                } catch (RuntimeException e) {
                     // don't bother with message
                 }
             }
@@ -2342,12 +2342,12 @@ public class SnarkManager implements CompleteListener {
         if ( (trackers == null) || (trackers.trim().length() <= 0) ) {
             setDefaultTrackerMap(true);
         } else {
-            String[] toks = trackers.split(",");
+            String[] toks = DataHelper.split(trackers, ",");
             for (int i = 0; i < toks.length; i += 2) {
                 String name = toks[i].trim().replace("&#44;", ",");
                 String url = toks[i+1].trim().replace("&#44;", ",");
                 if ( (name.length() > 0) && (url.length() > 0) ) {
-                    String urls[] = url.split("=", 2);
+                    String urls[] = DataHelper.split(url, "=", 2);
                     String url2 = urls.length > 1 ? urls[1] : "";
                     _trackerMap.put(name, new Tracker(name, urls[0], url2));
                 }
@@ -2367,7 +2367,7 @@ public class SnarkManager implements CompleteListener {
             String name = DEFAULT_TRACKERS[i];
             if (name.equals("TheBland") && !SigType.ECDSA_SHA256_P256.isAvailable())
                 continue;
-            String urls[] = DEFAULT_TRACKERS[i+1].split("=", 2);
+            String urls[] = DataHelper.split(DEFAULT_TRACKERS[i+1], "=", 2);
             String url2 = urls.length > 1 ? urls[1] : null;
             _trackerMap.put(name, new Tracker(name, urls[0], url2));
         }
@@ -2467,7 +2467,7 @@ public class SnarkManager implements CompleteListener {
         public void run() {
             try {
                 run2();
-            } catch (Exception e) {
+            } catch (RuntimeException e) {
                 _log.error("Error starting", e);
             }
         }
@@ -2595,7 +2595,7 @@ public class SnarkManager implements CompleteListener {
                 } else {
                     addMessageNoEscape(_t("Finished recheck of torrent {0}, unchanged", link));
                 }
-            } catch (Exception e) {
+            } catch (IOException e) {
                 _log.error("Error rechecking " + snark.getBaseName(), e);
                 addMessage(_t("Error checking the torrent {0}", snark.getBaseName()) + ": " + e);
             }
diff --git a/apps/i2psnark/java/src/org/klomp/snark/Storage.java b/apps/i2psnark/java/src/org/klomp/snark/Storage.java
index e0014c5051b027088a33d8b3ef18f5038164046a..0fff5b885ebc9f2b64233691e26aa780bd21072a 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/Storage.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/Storage.java
@@ -518,6 +518,31 @@ public class Storage implements Closeable
       return rv;
   }
 
+  /**
+   *  Call setPriority() for all changed files first,
+   *  then call this.
+   *  The length of all the pieces that are not yet downloaded,
+   *  and are set to skipped.
+   *  This is not the same as the total of all skipped files,
+   *  since pieces may span multiple files.
+   *
+   *  @return 0 on error, if complete, or if only one file
+   *  @since 0.9.24
+   */
+  public long getSkippedLength() {
+      int[] pri = getPiecePriorities();
+      if (pri == null)
+          return 0;
+      long rv = 0;
+      final int end = pri.length - 1;
+      for (int i = 0; i <= end; i++) {
+          if (pri[i] <= -9 && !bitfield.get(i)) {
+              rv += (i != end) ? piece_size : metainfo.getPieceLength(i);
+          }
+      }
+      return rv;
+  }
+
   /**
    * The BitField that tells which pieces this storage contains.
    * Do not change this since this is the current state of the storage.
@@ -748,7 +773,7 @@ public class Storage implements Closeable
                     }
                     rv = repl;
                 }
-            } catch (Exception ex) {
+            } catch (RuntimeException ex) {
                 ex.printStackTrace();
             }
         }
@@ -1483,7 +1508,7 @@ public class Storage implements Closeable
                   break;
             }  // switch
           } // while
-      } catch (Exception e) {
+      } catch (RuntimeException e) {
           e.printStackTrace();
           error = true;
       }
diff --git a/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java b/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java
index ab1116722b6c7319712d7c2514cf4c673c4bde79..fc48c6d87be8fa7b99827a06e3885071c447aefd 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java
@@ -23,8 +23,8 @@ package org.klomp.snark;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.net.MalformedURLException;
-import java.net.URL;
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -875,18 +875,20 @@ public class TrackerClient implements Runnable {
   }
 
   /**
-   *  @param ann an announce URL
+   *  @param ann an announce URL, may be null, returns false if null
    *  @return true for i2p hosts only
    *  @since 0.7.12
    */
   public static boolean isValidAnnounce(String ann) {
-    URL url;
+    if (ann == null)
+        return false;
+    URI url;
     try {
-       url = new URL(ann);
-    } catch (MalformedURLException mue) {
-       return false;
+        url = new URI(ann);
+    } catch (URISyntaxException use) {
+        return false;
     }
-    return url.getProtocol().equals("http") &&
+    return "http".equals(url.getScheme()) && url.getHost() != null &&
            (url.getHost().endsWith(".i2p") || url.getHost().equals("i2p"));
   }
 
@@ -896,13 +898,13 @@ public class TrackerClient implements Runnable {
    *  @since 0.9.5
    */
   private static Hash getHostHash(String ann) {
-    URL url;
+    URI url;
     try {
-        url = new URL(ann);
-    } catch (MalformedURLException mue) {
+        url = new URI(ann);
+    } catch (URISyntaxException use) {
         return null;
     }
-    if (!url.getProtocol().equals("http"))
+    if (!"http".equals(url.getScheme()))
         return null;
     String host = url.getHost();
     if (host.endsWith(".i2p"))
@@ -912,7 +914,7 @@ public class TrackerClient implements Runnable {
         if (path == null || path.length() < 517 ||
             !path.startsWith("/"))
             return null;
-        String[] parts = path.substring(1).split("[/\\?&;]", 2);
+        String[] parts = DataHelper.split(path.substring(1), "[/\\?&;]", 2);
         return ConvertToHash.getHash(parts[0]);
     }
     return null;
diff --git a/apps/i2psnark/java/src/org/klomp/snark/dht/NodeInfo.java b/apps/i2psnark/java/src/org/klomp/snark/dht/NodeInfo.java
index c6fce8f14461259a6b52739af3574e09cf9db27a..708d2d27661272c69d5050d626d4e4e1bab9e6c0 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/dht/NodeInfo.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/dht/NodeInfo.java
@@ -102,7 +102,7 @@ class NodeInfo extends SimpleDataStructure {
      */
     public NodeInfo(String s) throws DataFormatException {
         super();
-        String[] parts = s.split(":", 4);
+        String[] parts = DataHelper.split(s, ":", 4);
         if (parts.length != 4)
             throw new DataFormatException("Bad format");
         byte[] nid = Base64.decode(parts[0]);
@@ -225,7 +225,7 @@ class NodeInfo extends SimpleDataStructure {
             NodeInfo ni = (NodeInfo) o;
             // assume dest matches, ignore it
             return this.hash.equals(ni.hash) && nID.equals(ni.nID) && port == ni.port;
-        } catch (Exception e) {
+        } catch (RuntimeException e) {
             return false;
         }
     }
diff --git a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java
index b0335f6a8c3ce096535fd12e5854dff009235482..4d7727729b4be3396a257917361b66242733f4d5 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java
@@ -18,7 +18,6 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
-import java.util.TimeZone;
 import java.util.TreeMap;
 
 import javax.servlet.ServletConfig;
@@ -2714,7 +2713,7 @@ public class I2PSnarkServlet extends BasicServlet {
         if (snark != null) {
             // first table - torrent info
             buf.append("<table class=\"snarkTorrentInfo\">\n");
-            buf.append("<tr><th><b>")
+            buf.append("<tr><th></th><th><b>")
                .append(_t("Torrent"))
                .append(":</b> ")
                .append(DataHelper.escapeHTML(snark.getBaseName()))
@@ -2724,7 +2723,7 @@ public class I2PSnarkServlet extends BasicServlet {
             String baseName = encodePath((new File(fullPath)).getName());
             buf.append("<tr><td>");
             toThemeImg(buf, "file");
-            buf.append("&nbsp;<b>")
+            buf.append("</td><td><b>")
                .append(_t("Torrent file"))
                .append(":</b> <a href=\"").append(_contextPath).append('/').append(baseName).append("\">")
                .append(DataHelper.escapeHTML(fullPath))
@@ -2732,7 +2731,7 @@ public class I2PSnarkServlet extends BasicServlet {
             if (snark.getStorage() != null) {
                 buf.append("<tr><td>");
                 toThemeImg(buf, "file");
-                buf.append("&nbsp;<b>")
+                buf.append("</td><td><b>")
                    .append(_t("Data location"))
                    .append(":</b> ")
                    .append(DataHelper.escapeHTML(snark.getStorage().getBase().getPath()))
@@ -2741,7 +2740,7 @@ public class I2PSnarkServlet extends BasicServlet {
             String hex = I2PSnarkUtil.toHex(snark.getInfoHash());
             buf.append("<tr><td>");
             toThemeImg(buf, "details");
-            buf.append("&nbsp;<b>")
+            buf.append("</td><td><b>")
                .append(_t("Info hash"))
                .append(":</b> ")
                .append(hex.toUpperCase(Locale.US))
@@ -2761,7 +2760,7 @@ public class I2PSnarkServlet extends BasicServlet {
                         buf.append(trackerLink);
                     else
                         toThemeImg(buf, "details");
-                    buf.append(" <b>").append(_t("Primary Tracker")).append(":</b> ");
+                    buf.append("</td><td><b>").append(_t("Primary Tracker")).append(":</b> ");
                     buf.append(getShortTrackerLink(announce, snark.getInfoHash()));
                     buf.append("</td></tr>");
                 }
@@ -2769,7 +2768,7 @@ public class I2PSnarkServlet extends BasicServlet {
                 if (alist != null && !alist.isEmpty()) {
                     buf.append("<tr><td>");
                     toThemeImg(buf, "details");
-                    buf.append(" <b>")
+                    buf.append("</td><td valign=\"top\"><b>")
                        .append(_t("Tracker List")).append(":</b> ");
                     for (List<String> alist2 : alist) {
                         buf.append('[');
@@ -2794,21 +2793,19 @@ public class I2PSnarkServlet extends BasicServlet {
                         com = com.substring(0, 1024);
                     buf.append("<tr><td>");
                     toThemeImg(buf, "details");
-                    buf.append(" <b>")
+                    buf.append("</td><td><b>")
                        .append(_t("Comment")).append(":</b> ")
                        .append(DataHelper.stripHTML(com))
                        .append("</td></tr>\n");
                 }
                 long dat = meta.getCreationDate();
                 SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm");
-                String systemTimeZone = _context.getProperty("i2p.systemTimeZone");
-                if (systemTimeZone != null)
-                    fmt.setTimeZone(TimeZone.getTimeZone(systemTimeZone));
+                fmt.setTimeZone(DataHelper.getSystemTimeZone(_context));
                 if (dat > 0) {
                     String date = fmt.format(new Date(dat));
                     buf.append("<tr><td>");
                     toThemeImg(buf, "details");
-                    buf.append(" <b>")
+                    buf.append("</td><td><b>")
                        .append(_t("Created")).append(":</b> ")
                        .append(date)
                        .append("</td></tr>\n");
@@ -2819,7 +2816,7 @@ public class I2PSnarkServlet extends BasicServlet {
                         cby = com.substring(0, 128);
                     buf.append("<tr><td>");
                     toThemeImg(buf, "details");
-                    buf.append(" <b>")
+                    buf.append("</td><td><b>")
                        .append(_t("Created By")).append(":</b> ")
                        .append(DataHelper.stripHTML(cby))
                        .append("</td></tr>\n");
@@ -2829,7 +2826,7 @@ public class I2PSnarkServlet extends BasicServlet {
                     String date = fmt.format(new Date(dates[0]));
                     buf.append("<tr><td>");
                     toThemeImg(buf, "details");
-                    buf.append(" <b>")
+                    buf.append("</td><td><b>")
                        .append(_t("Added")).append(":</b> ")
                        .append(date)
                        .append("</td></tr>\n");
@@ -2838,7 +2835,7 @@ public class I2PSnarkServlet extends BasicServlet {
                     String date = fmt.format(new Date(dates[1]));
                     buf.append("<tr><td>");
                     toThemeImg(buf, "details");
-                    buf.append(" <b>")
+                    buf.append("</td><td><b>")
                        .append(_t("Completed")).append(":</b> ")
                        .append(date)
                        .append("</td></tr>\n");
@@ -2852,7 +2849,7 @@ public class I2PSnarkServlet extends BasicServlet {
                     buf.append("&amp;tr=").append(announce);
                 buf.append("\">")
                    .append(toImg("magnet", _t("Magnet link")))
-                   .append("</a> <b>Magnet:</b> <a href=\"")
+                   .append("</a></td><td><b>Magnet:</b> <a href=\"")
                    .append(MagnetURI.MAGNET_FULL).append(hex);
                 if (announce != null)
                     buf.append("&amp;tr=").append(announce);
@@ -2863,7 +2860,9 @@ public class I2PSnarkServlet extends BasicServlet {
                 buf.append("</a>")
                    .append("</td></tr>\n");
             } else {
-                buf.append("<tr><td>")
+                buf.append("<tr><td>");
+                toThemeImg(buf, "details");
+                buf.append("</td><td><b>")
                    .append(_t("Private torrent"))
                    .append("</td></tr>\n");
             }
@@ -2874,7 +2873,7 @@ public class I2PSnarkServlet extends BasicServlet {
 
             buf.append("<tr><td>");
             toThemeImg(buf, "size");
-            buf.append("&nbsp;<b>")
+            buf.append("</td><td><b>")
                .append(_t("Size"))
                .append(":</b> ")
                .append(formatSize(snark.getTotalLength()));
@@ -2917,6 +2916,15 @@ public class I2PSnarkServlet extends BasicServlet {
                    .append(":</b> ")
                    .append(formatSize(needed));
             }
+            long skipped = snark.getSkippedLength();
+            if (skipped > 0) {
+                buf.append("&nbsp;");
+                toThemeImg(buf, "head_rx");
+                buf.append("&nbsp;<b>")
+                   .append(_t("Skipped"))
+                   .append(":</b> ")
+                   .append(formatSize(skipped));
+            }
             if (meta != null) {
                 List<List<String>> files = meta.getFiles();
                 int fileCount = files != null ? files.size() : 1;
@@ -2943,20 +2951,19 @@ public class I2PSnarkServlet extends BasicServlet {
 
             // buttons
             if (showStopStart) {
-                buf.append("<tr><td>");
-                toThemeImg(buf, "file");
+                buf.append("<tr><td></td><td>");
                 if (snark.isChecking()) {
-                    buf.append("&nbsp;<b>").append(_t("Checking")).append("&hellip; ")
+                    buf.append("<b>").append(_t("Checking")).append("&hellip; ")
                        .append((new DecimalFormat("0.00%")).format(snark.getCheckingProgress()))
                        .append("&nbsp;&nbsp;&nbsp;<a href=\"").append(base).append("\">")
                        .append(_t("Refresh page for results")).append("</a>");
                 } else if (snark.isStarting()) {
-                    buf.append("&nbsp;<b>").append(_t("Starting")).append("&hellip;</b>");
+                    buf.append("<b>").append(_t("Starting")).append("&hellip;</b>");
                 } else if (snark.isAllocating()) {
-                    buf.append("&nbsp;<b>").append(_t("Allocating")).append("&hellip;</b>");
+                    buf.append("<b>").append(_t("Allocating")).append("&hellip;</b>");
                 } else {
                     boolean isRunning = !snark.isStopped();
-                    buf.append(" <input type=\"submit\" value=\"");
+                    buf.append("<input type=\"submit\" value=\"");
                     if (isRunning)
                         buf.append(_t("Stop")).append("\" name=\"stop\" class=\"stoptorrent\">\n");
                     else
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ConnThrottler.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ConnThrottler.java
index c802a999585859359d463a8c0a053e3bae8e1160..de9188d8e516c9caf393dbf8cdbe9fe779ec9513 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ConnThrottler.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ConnThrottler.java
@@ -7,7 +7,6 @@ import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.TimeZone;
 
 import net.i2p.I2PAppContext;
 import net.i2p.data.DataHelper;
@@ -57,9 +56,7 @@ class ConnThrottler {
         _log = log;
         // for logging
         _fmt = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM);
-        String systemTimeZone = I2PAppContext.getGlobalContext().getProperty("i2p.systemTimeZone");
-        if (systemTimeZone != null)
-            _fmt.setTimeZone(TimeZone.getTimeZone(systemTimeZone));
+        _fmt.setTimeZone(DataHelper.getSystemTimeZone(I2PAppContext.getGlobalContext()));
         new Cleaner();
     }
 
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/GunzipOutputStream.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/GunzipOutputStream.java
index e0798567d887fdbd8847c3b4d01d64682a91de6f..6f5a4c3a712815684278d1c9ce676d0a32499885 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/GunzipOutputStream.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/GunzipOutputStream.java
@@ -105,7 +105,7 @@ class GunzipOutputStream extends InflaterOutputStream {
     public long getTotalRead() {
         try {
             return inf.getBytesRead(); 
-        } catch (Exception e) {
+        } catch (RuntimeException e) {
             return 0;
         }
     }
@@ -116,7 +116,7 @@ class GunzipOutputStream extends InflaterOutputStream {
     public long getTotalExpanded() { 
         try {
             return inf.getBytesWritten(); 
-        } catch (Exception e) {
+        } catch (RuntimeException e) {
             // possible NPE in some implementations
             return 0;
         }
@@ -128,7 +128,7 @@ class GunzipOutputStream extends InflaterOutputStream {
     public long getRemaining() { 
         try {
             return inf.getRemaining(); 
-        } catch (Exception e) {
+        } catch (RuntimeException e) {
             // possible NPE in some implementations
             return 0;
         }
@@ -140,7 +140,7 @@ class GunzipOutputStream extends InflaterOutputStream {
     public boolean getFinished() { 
         try {
             return inf.finished(); 
-        } catch (Exception e) {
+        } catch (RuntimeException e) {
             // possible NPE in some implementations
             return true;
         }
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java
index 7c006207558158459b6e03ee312204a91a47c005..e8680e3d31e8de36887120fd0cc10804ca0accd2 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java
@@ -1873,7 +1873,7 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
             try {
                 result.fromByteArray(content);
                 return result;
-            } catch (Exception ex) {
+            } catch (RuntimeException ex) {
                 if (log.shouldLog(Log.INFO)) 
                     log.info("File is not a binary destination - trying base64");
                 try {
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClient.java
index 93785e32659d957842660dfb0d96ef8c4c3d1989..4b1284cfecae464b554b63f15c597bd8fe595799 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClient.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClient.java
@@ -3,6 +3,7 @@
  */
 package net.i2p.i2ptunnel;
 
+import java.io.IOException;
 import java.net.Socket;
 import java.net.UnknownHostException;
 import java.util.ArrayList;
@@ -10,6 +11,7 @@ import java.util.List;
 import java.util.Properties;
 import java.util.StringTokenizer;
 
+import net.i2p.I2PException;
 import net.i2p.client.streaming.I2PSocket;
 import net.i2p.client.streaming.I2PSocketAddress;
 import net.i2p.data.Destination;
@@ -122,7 +124,17 @@ public class I2PTunnelClient extends I2PTunnelClientBase {
             // we are called from an unlimited thread pool, so run inline
             //t.start();
             t.run();
-        } catch (Exception ex) {
+        } catch (IOException ex) {
+            if (_log.shouldLog(Log.INFO))
+                _log.info("Error connecting", ex);
+            //l.log("Error connecting: " + ex.getMessage());
+            closeSocket(s);
+            if (i2ps != null) {
+                synchronized (sockLock) {
+                    mySockets.remove(sockLock);
+                }
+            }
+        } catch (I2PException ex) {
             if (_log.shouldLog(Log.INFO))
                 _log.info("Error connecting", ex);
             //l.log("Error connecting: " + ex.getMessage());
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java
index 4183ce39917fe0906137b3d3a64e851f950a59c5..19ebd8b7c5d5bcec90b2c386d61dc5f8f98a48b9 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java
@@ -414,7 +414,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
                         _log.debug(getPrefix(requestId) + "First line [" + line + "]");
                     }
 
-                    String[] params = line.split(" ", 3);
+                    String[] params = DataHelper.split(line, " ", 3);
                     if(params.length != 3) {
                         break;
                     }
@@ -1252,7 +1252,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
         String s = getTunnel().getClientOptions().getProperty(PROP_SSL_OUTPROXIES);
         if (s == null)
             return null;
-        String[] p = s.split("[,; \r\n\t]");
+        String[] p = DataHelper.split(s, "[,; \r\n\t]");
         if (p.length == 0)
             return null;
         // todo doesn't check for ""
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientBase.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientBase.java
index 5e39cf3b8388df891824c91ed1d03c1ddaf4c4cd..e68e38d6b8504b431a66917775e312fe6cbd901e 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientBase.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientBase.java
@@ -285,7 +285,7 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
                 // We send Accept-Charset: UTF-8 in the 407 so hopefully it comes back that way inside the B64 ?
                 try {
                     String dec = new String(decoded, "UTF-8");
-                    String[] parts = dec.split(":");
+                    String[] parts = DataHelper.split(dec, ":");
                     String user = parts[0];
                     String pw = parts[1];
                     // first try pw for that user
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java
index 35dd6f1326d5332200bd6933d25eb56d32b36e1a..2ae6abba466bc56057d7445858210e73d9b7cf80 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java
@@ -664,7 +664,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
          */
         @Override
         protected String filterResponseLine(String line) {
-            String[] s = line.split(" ", 3);
+            String[] s = DataHelper.split(line, " ", 3);
             if (s.length > 1 &&
                 (s[1].startsWith("3") || s[1].startsWith("5")))
                 _dataExpected = 0;
@@ -742,7 +742,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
         public long getTotalRead() { 
             try {
                 return def.getTotalIn();
-            } catch (Exception e) {
+            } catch (RuntimeException e) {
                 // j2se 1.4.2_08 on linux is sometimes throwing an NPE in the getTotalIn() implementation
                 return 0; 
             }
@@ -750,7 +750,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
         public long getTotalCompressed() { 
             try {
                 return def.getTotalOut();
-            } catch (Exception e) {
+            } catch (RuntimeException e) {
                 // j2se 1.4.2_08 on linux is sometimes throwing an NPE in the getTotalOut() implementation
                 return 0;
             }
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCClient.java
index 7f4c2c22ac45bbf279a814ac9bdffbbc6f51eec2..f53a72cd1221c464f5b9dc2194e6b44de47d1773 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCClient.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCClient.java
@@ -8,6 +8,7 @@ import java.util.List;
 import java.util.Properties;
 import java.util.StringTokenizer;
 
+import net.i2p.I2PException;
 import net.i2p.client.streaming.I2PSocket;
 import net.i2p.client.streaming.I2PSocketAddress;
 import net.i2p.data.DataHelper;
@@ -142,7 +143,7 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase {
             // we are called from an unlimited thread pool, so run inline
             //out.start();
             out.run();
-        } catch (Exception ex) {
+        } catch (IOException ex) {
             // generally NoRouteToHostException
             if (_log.shouldLog(Log.WARN))
                 _log.warn("Error connecting", ex);
@@ -160,6 +161,23 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase {
                     mySockets.remove(sockLock);
                 }
             }
+        } catch (I2PException ex) {
+            if (_log.shouldLog(Log.WARN))
+                _log.warn("Error connecting", ex);
+            //l.log("Error connecting: " + ex.getMessage());
+            try {
+                // Send a response so the user doesn't just see a disconnect
+                // and blame his router or the network.
+                String name = addr != null ? addr.getHostName() : "undefined";
+                String msg = ":" + name + " 499 you :" + ex + "\r\n";
+                s.getOutputStream().write(DataHelper.getUTF8(msg));
+            } catch (IOException ioe) {}
+            closeSocket(s);
+            if (i2ps != null) {
+                synchronized (sockLock) {
+                    mySockets.remove(sockLock);
+                }
+            }
         }
 
     }
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java
index 2288c27ec2be9efb4d4e9c89d391d181729d3263..79d974e9061adafa84ea6a6c765dff56cb669f43 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java
@@ -13,6 +13,7 @@ import java.util.Properties;
 
 import net.i2p.client.streaming.I2PSocket;
 import net.i2p.crypto.SHA256Generator;
+import net.i2p.data.DataHelper;
 import net.i2p.data.Destination;
 import net.i2p.data.Hash;
 import net.i2p.data.Base32;
@@ -277,7 +278,7 @@ public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable {
             //if (_log.shouldLog(Log.DEBUG))
             //    _log.debug("Got line: " + s);
 
-            String field[]=s.split(" ",5);
+            String field[] = DataHelper.split(s, " ", 5);
             String command;
             int idx=0;
 
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelOutproxyRunner.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelOutproxyRunner.java
index 2ed668b7a4f8c608b1a8a946847c1a55efe32f71..d8d9369a0ae2a0549ac2a05d88c656730813c49b 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelOutproxyRunner.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelOutproxyRunner.java
@@ -182,7 +182,7 @@ public class I2PTunnelOutproxyRunner extends I2PAppThread {
         } catch (IllegalStateException ise) {
             if (_log.shouldLog(Log.WARN))
                 _log.warn("gnu?", ise);
-        } catch (Exception e) {
+        } catch (RuntimeException e) {
             if (_log.shouldLog(Log.ERROR))
                 _log.error("Internal error", e);
         } finally {
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelRunner.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelRunner.java
index 2acd7cfc8b9d60463e03249216e6a4f73c0a7eea..b8bfdaebfe9f25e945e17c5745081c7f2eff0bbd 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelRunner.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelRunner.java
@@ -326,7 +326,7 @@ public class I2PTunnelRunner extends I2PAppThread implements I2PSocket.SocketErr
 		//   at net.i2p.i2ptunnel.I2PTunnelRunner.run(I2PTunnelRunner.java:167)
             if (_log.shouldLog(Log.WARN))
                 _log.warn("gnu?", ise);
-        } catch (Exception e) {
+        } catch (RuntimeException e) {
             if (_log.shouldLog(Log.ERROR))
                 _log.error("Internal error", e);
         } finally {
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java
index 76a52c32b977b9722339730ef1b34282d23bc0fe..3b9217238337701111b9504dfc7f349ab3a79aee 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java
@@ -517,7 +517,7 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
                 break;
             } catch(SocketTimeoutException ste) {
                 // ignored, we never set the timeout
-            } catch (Exception e) {
+            } catch (RuntimeException e) {
                 // streaming borkage
                 if (_log.shouldLog(Log.ERROR))
                     _log.error("Uncaught exception accepting", e);
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2Ping.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2Ping.java
index dd59042d8f1a1db3bd8e7dfa12b9a71fb20a14ae..da615301e32a776fe0eb4e06576c3586196825fa 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2Ping.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2Ping.java
@@ -19,6 +19,7 @@ import net.i2p.I2PException;
 import net.i2p.client.I2PSession;
 import net.i2p.client.I2PSessionException;
 import net.i2p.client.streaming.I2PSocketManager;
+import net.i2p.data.DataHelper;
 import net.i2p.data.Destination;
 import net.i2p.util.EventDispatcher;
 import net.i2p.util.I2PAppThread;
@@ -93,7 +94,7 @@ public class I2Ping extends I2PTunnelClientBase {
       int localPort = 0;
       int remotePort = 0;
       boolean error = false;
-      String[] argv = cmd.split(" ");
+      String[] argv = DataHelper.split(cmd, " ");
       Getopt g = new Getopt("ping", argv, "t:m:n:chl:f:p:");
       int c;
       while ((c = g.getopt()) != -1) {
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java
index d5ca354c59b24efef46a00da5d8f7732a097c7e6..4a6e85211f99aea2a0680a7a8bf2f5231f0b37fc 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java
@@ -230,7 +230,7 @@ public class TunnelController implements Logging {
         }
         try {
             doStartTunnel();
-        } catch (Exception e) {
+        } catch (RuntimeException e) {
             _log.error("Error starting the tunnel " + getName(), e);
             log("Error starting the tunnel " + getName() + ": " + e.getMessage());
             // if we don't acquire() then the release() in stopTunnel() won't work
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/irc/I2PTunnelDCCClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/irc/I2PTunnelDCCClient.java
index f5b434e3be058c4d1ecff900da526e27bb4addfb..24ac12e61bfe0abad9ced0713c80f0da62832819 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/irc/I2PTunnelDCCClient.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/irc/I2PTunnelDCCClient.java
@@ -6,6 +6,7 @@ package net.i2p.i2ptunnel.irc;
 import java.net.Socket;
 import java.io.IOException;
 
+import net.i2p.I2PException;
 import net.i2p.client.streaming.I2PSocket;
 import net.i2p.client.streaming.I2PSocketManager;
 import net.i2p.client.streaming.I2PSocketOptions;
@@ -80,7 +81,14 @@ public class I2PTunnelDCCClient extends I2PTunnelClientBase {
             // we are called from an unlimited thread pool, so run inline
             //t.start();
             t.run();
-        } catch (Exception ex) {
+        } catch (IOException ex) {
+            _log.error("Could not make DCC connection to " + _dest + ':' + _remotePort, ex);
+            closeSocket(s);
+            if (i2ps != null) {
+                try { i2ps.close(); } catch (IOException ioe) {}
+            }
+            notifyEvent(CONNECT_STOP_EVENT, Integer.valueOf(getLocalPort()));
+        } catch (I2PException ex) {
             _log.error("Could not make DCC connection to " + _dest + ':' + _remotePort, ex);
             closeSocket(s);
             if (i2ps != null) {
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/irc/IRCFilter.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/irc/IRCFilter.java
index 3112c9ddaaafc01ec4f9f40b31d101c7bab65214..eef558837e14fca5248a52121420600e8b0a08b6 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/irc/IRCFilter.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/irc/IRCFilter.java
@@ -33,7 +33,7 @@ abstract class IRCFilter {
      */
     public static String inboundFilter(String s, StringBuffer expectedPong, DCCHelper helper) {
         
-        String field[]=s.split(" ",4);
+        String field[] = DataHelper.split(s, " ", 4);
         String command;
         int idx=0;
         final String[] allowedCommands =
@@ -274,7 +274,7 @@ abstract class IRCFilter {
      */
     public static String outboundFilter(String s, StringBuffer expectedPong, DCCHelper helper) {
 
-        String field[]=s.split(" ",3);
+        String field[] = DataHelper.split(s, " ",3);
 
         if(field[0].length()==0)
             return null; // W T F?
@@ -420,7 +420,7 @@ abstract class IRCFilter {
         int ctcp = msg.indexOf(0x01);
         if (ctcp > 0)
             msg = msg.substring(0, ctcp);
-        String[] args = msg.split(" ", 5);
+        String[] args = DataHelper.split(msg, " ", 5);
         if (args.length <= 0)
             return null;
         String type = args[0];
@@ -512,7 +512,7 @@ abstract class IRCFilter {
         int ctcp = msg.indexOf(0x01);
         if (ctcp > 0)
             msg = msg.substring(0, ctcp);
-        String[] args = msg.split(" ", 5);
+        String[] args = DataHelper.split(msg, " ", 5);
         if (args.length <= 0)
             return null;
         String type = args[0];
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ui/GeneralHelper.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ui/GeneralHelper.java
index b2b38398eb71e919759f5d8d5d01385915b48c38..eb481f6ea61e28303651a6df1d786f5c27b04303 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ui/GeneralHelper.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ui/GeneralHelper.java
@@ -9,6 +9,7 @@ import java.util.Properties;
 import java.util.TreeMap;
 
 import net.i2p.I2PAppContext;
+import net.i2p.I2PException;
 import net.i2p.client.I2PClient;
 import net.i2p.crypto.SigType;
 import net.i2p.data.DataHelper;
@@ -341,7 +342,8 @@ public class GeneralHelper {
                     rv = pkf.getDestination();
                     if (rv != null)
                         return rv;
-                } catch (Exception e) {}
+                } catch (I2PException e) {
+                } catch (IOException e) {}
             }
         }
         return null;
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java
index fb9b137b22755d0deecb24b23ac7e186522abd6b..d960dc93a29c6542a58d3c3120e57a1f7785318c 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java
@@ -8,8 +8,11 @@ package net.i2p.i2ptunnel.web;
  *
  */
 
+import java.io.IOException;
 import java.util.List;
 import java.util.Set;
+
+import net.i2p.I2PException;
 import net.i2p.crypto.SigType;
 import net.i2p.data.Base64;
 import net.i2p.data.DataHelper;
@@ -87,7 +90,8 @@ public class EditBean extends IndexBean {
                 //System.err.println("Signing " + spoof + " with " + Base64.encode(privKey.getData()));
                 Signature sig = _context.dsa().sign(spoof.getBytes("UTF-8"), privKey);
                 return Base64.encode(sig.getData());
-            } catch (Exception e) {}
+            } catch (I2PException e) {
+            } catch (IOException e) {}
         }
         return "";
     }
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java
index 15eb3f29ee57a1fc6f3951f4a432e2f3451cf2be..2f2509c08aac98e2726f8b1421e259e7df261d86 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java
@@ -9,12 +9,14 @@ package net.i2p.i2ptunnel.web;
  */
 
 import java.io.File;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
 import java.util.Properties;
 
 import net.i2p.I2PAppContext;
+import net.i2p.I2PException;
 import net.i2p.app.ClientAppManager;
 import net.i2p.app.Outproxy;
 import net.i2p.data.Certificate;
@@ -266,7 +268,7 @@ public class IndexBean {
         if (_action != null) {
             try {
                 buf.append(processAction()).append('\n');
-            } catch (Exception e) {
+            } catch (RuntimeException e) {
                 _log.log(Log.CRIT, "Error processing " + _action, e);
                 buf.append("Error: ").append(e.toString()).append('\n');
             }
@@ -972,7 +974,9 @@ public class IndexBean {
         PrivateKeyFile pkf = new PrivateKeyFile(keyFile);
         try {
             pkf.createIfAbsent();
-        } catch (Exception e) {
+        } catch (I2PException e) {
+            return "Create private key file failed: " + e;
+        } catch (IOException e) {
             return "Create private key file failed: " + e;
         }
         switch (_certType) {
@@ -1011,7 +1015,9 @@ public class IndexBean {
         try {
             pkf.write();
             newdest = pkf.getDestination();
-        } catch (Exception e) {
+        } catch (I2PException e) {
+            return "Modification failed: " + e;
+        } catch (IOException e) {
             return "Modification failed: " + e;
         }
         return "Destination modified - " +
diff --git a/apps/jetty/build.xml b/apps/jetty/build.xml
index 2fad37dfafe9fe236bea7bb9125e3506bd78aa35..b1fdbcd461996716c13d2e4277b6bf07d2c1c9b6 100644
--- a/apps/jetty/build.xml
+++ b/apps/jetty/build.xml
@@ -149,9 +149,14 @@
         <!-- commons-logging.jar not in Jetty 6 but we have it in launch4j so copy it over,
              needed for old plugins and things. We add tomcat-juli below.
           -->
+      <!--
+        * Removed in 0.9.24, see ticket #1679
+        * Jetty now uses tomcat-juli (added below to commons-logging.jar), not commons-logging proper,
+        * and no known plugins use it either.
         <jar destfile="jettylib/commons-logging.jar" filesetmanifest="mergewithoutmain" >
             <zipfileset excludes="META-INF/LICENSE.txt META-INF/NOTICE.txt" src="../../installer/lib/launch4j/lib/commons-logging.jar" />
         </jar>
+      -->
         <ant target="copyTomcatLib" />
     </target>
 
diff --git a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketEepGet.java b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketEepGet.java
index 1b67bd2261ede4089c1018e36541057a28d64f68..73ede8bb057acb4b05e333e50d700ee3b029c6c1 100644
--- a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketEepGet.java
+++ b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketEepGet.java
@@ -5,7 +5,8 @@ import java.io.IOException;
 import java.io.OutputStream;
 import java.net.MalformedURLException;
 import java.net.UnknownHostException;
-import java.net.URL;
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.util.Locale;
 import java.util.Properties;
 
@@ -112,8 +113,8 @@ public class I2PSocketEepGet extends EepGet {
         if (_socket != null) try { _socket.close(); } catch (IOException ioe) {}
 
         try {
-            URL url = new URL(_actualURL);
-            if ("http".equals(url.getProtocol())) {
+            URI url = new URI(_actualURL);
+            if ("http".equals(url.getScheme())) {
                 String host = url.getHost();
                 int port = url.getPort();
                 if (port <= 0 || port > 65535)
@@ -123,13 +124,16 @@ public class I2PSocketEepGet extends EepGet {
                 // Rewrite the url to strip out the /i2p/,
                 // as the naming service accepts B64KEY (but not B64KEY.i2p atm)
                 if ("i2p".equals(host)) {
-                    String file = url.getFile();
+                    String file = url.getRawPath();
                     try {
                         int slash = 1 + file.substring(1).indexOf("/");
                         host = file.substring(1, slash);
                         _actualURL = "http://" + host + file.substring(slash);
+                        String query = url.getRawQuery();
+                        if (query != null)
+                            _actualURL = _actualURL + '?' + query;
                     } catch (IndexOutOfBoundsException ioobe) {
-                        throw new IOException("Bad /i2p/ format: " + _actualURL);
+                        throw new MalformedURLException("Bad /i2p/ format: " + _actualURL);
                     }
                 }
 
@@ -173,12 +177,14 @@ public class I2PSocketEepGet extends EepGet {
                 opts.setPort(port);
                 _socket = _socketManager.connect(dest, opts);
             } else {
-                throw new IOException("Unsupported protocol: " + _actualURL);
+                throw new MalformedURLException("Unsupported protocol: " + _actualURL);
             }
-        } catch (MalformedURLException mue) {
-            throw new IOException("Request URL is invalid: " + _actualURL);
+        } catch (URISyntaxException use) {
+            IOException ioe = new MalformedURLException("Bad URL");
+            ioe.initCause(use);
+            throw ioe;
         } catch (I2PException ie) {
-            throw new IOException(ie.toString());
+            throw new IOException("I2P error", ie);
         }
 
         _proxyIn = _socket.getInputStream();
@@ -202,10 +208,17 @@ public class I2PSocketEepGet extends EepGet {
     @Override
     protected String getRequest() throws IOException {
         StringBuilder buf = new StringBuilder(2048);
-        URL url = new URL(_actualURL);
+        URI url;
+        try {
+            url = new URI(_actualURL);
+        } catch (URISyntaxException use) {
+            IOException ioe = new MalformedURLException("Bad URL");
+            ioe.initCause(use);
+            throw ioe;
+        }
         //String host = url.getHost();
-        String path = url.getPath();
-        String query = url.getQuery();
+        String path = url.getRawPath();
+        String query = url.getRawQuery();
         if (query != null)
             path = path + '?' + query;
         if (!path.startsWith("/"))
@@ -232,6 +245,8 @@ public class I2PSocketEepGet extends EepGet {
         if(!uaOverridden)
             buf.append("User-Agent: " + USER_AGENT + "\r\n");
         buf.append("\r\n");
+        if (_log.shouldDebug())
+            _log.debug("Request: [" + buf.toString() + "]");
         return buf.toString();
     }
 
@@ -264,7 +279,7 @@ public class I2PSocketEepGet extends EepGet {
                     url = args[i];
                 }
             }
-        } catch (Exception e) {
+        } catch (RuntimeException e) {
             e.printStackTrace();
             usage();
             return;
diff --git a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketManagerFactory.java b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketManagerFactory.java
index 91c18cfe42e7bf477bbce3fcecbf5254e1e1796e..4e6d3b51fa7013e65a6de7c78f723c2d6bd4fd11 100644
--- a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketManagerFactory.java
+++ b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketManagerFactory.java
@@ -195,7 +195,9 @@ public class I2PSocketManagerFactory {
             ByteArrayOutputStream keyStream = new ByteArrayOutputStream(1024);
             try {
                 client.createDestination(keyStream, getSigType(opts));
-            } catch (Exception e) {
+            } catch (I2PException e) {
+                throw new I2PSessionException("Error creating keys", e);
+            } catch (IOException e) {
                 throw new I2PSessionException("Error creating keys", e);
             }
             myPrivateKeyStream = new ByteArrayInputStream(keyStream.toByteArray());
diff --git a/apps/routerconsole/java/src/net/i2p/router/news/NewsManager.java b/apps/routerconsole/java/src/net/i2p/router/news/NewsManager.java
index d85baadf5837be58154778a2d5c4e64bb94e11c6..35da6b38745075b692301c165a3bcca31f6a3902 100644
--- a/apps/routerconsole/java/src/net/i2p/router/news/NewsManager.java
+++ b/apps/routerconsole/java/src/net/i2p/router/news/NewsManager.java
@@ -10,13 +10,13 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Date;
 import java.util.List;
-import java.util.TimeZone;
 
 import net.i2p.I2PAppContext;
+import net.i2p.app.ClientApp;
 import net.i2p.app.ClientAppManager;
 import net.i2p.app.ClientAppState;
 import static net.i2p.app.ClientAppState.*;
-import net.i2p.router.app.RouterApp;
+import net.i2p.data.DataHelper;
 import net.i2p.util.FileUtil;
 import net.i2p.util.Log;
 import net.i2p.util.TranslateReader;
@@ -30,7 +30,7 @@ import org.cybergarage.xml.Node;
  *
  *  @since 0.9.23
  */
-public class NewsManager implements RouterApp {
+public class NewsManager implements ClientApp {
 
     private final I2PAppContext _context;
     private final Log _log;
@@ -233,9 +233,7 @@ public class NewsManager implements RouterApp {
                     //  Doesn't work if the date has a : in it, but SHORT hopefully does not
                     DateFormat fmt = DateFormat.getDateInstance(DateFormat.SHORT);
                     // the router sets the JVM time zone to UTC but saves the original here so we can get it
-                    String systemTimeZone = _context.getProperty("i2p.systemTimeZone");
-                    if (systemTimeZone != null)
-                        fmt.setTimeZone(TimeZone.getTimeZone(systemTimeZone));
+                    fmt.setTimeZone(DataHelper.getSystemTimeZone(_context));
                     try {
                         Date date = fmt.parse(newsContent.substring(0, colon));
                         entry.updated = date.getTime();
diff --git a/apps/routerconsole/java/src/net/i2p/router/update/NewsFetcher.java b/apps/routerconsole/java/src/net/i2p/router/update/NewsFetcher.java
index 66799737dbf6ea44355f6a0d0fa8978d5a9f7e3f..5c800bf038cd5a015feddeb8add938fac4415977 100644
--- a/apps/routerconsole/java/src/net/i2p/router/update/NewsFetcher.java
+++ b/apps/routerconsole/java/src/net/i2p/router/update/NewsFetcher.java
@@ -19,7 +19,6 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.StringTokenizer;
-import java.util.TimeZone;
 
 import net.i2p.app.ClientAppManager;
 import net.i2p.crypto.SU3File;
@@ -226,6 +225,18 @@ class NewsFetcher extends UpdateRunner {
                                 _mgr.notifyVersionConstraint(this, _currentURI, ROUTER_SIGNED, "", ver, msg);
                                 return;
                             }
+                            if (!FileUtil.isPack200Supported()) {
+                                String msg = _mgr._t("No Pack200 support in Java runtime.");
+                                _log.logAlways(Log.WARN, "Cannot update to version " + ver + ": " + msg);
+                                _mgr.notifyVersionConstraint(this, _currentURI, ROUTER_SIGNED, "", ver, msg);
+                                return;
+                            }
+                            if (!ConfigUpdateHandler.USE_SU3_UPDATE) {
+                                String msg = _mgr._t("No update certificates installed.");
+                                _log.logAlways(Log.WARN, "Cannot update to version " + ver + ": " + msg);
+                                _mgr.notifyVersionConstraint(this, _currentURI, ROUTER_SIGNED, "", ver, msg);
+                                return;
+                            }
                             String minRouter = args.get(MIN_VERSION_KEY);
                             if (minRouter != null) {
                                 if (VersionComparator.comp(RouterVersion.VERSION, minRouter) < 0) {
@@ -252,7 +263,7 @@ class NewsFetcher extends UpdateRunner {
                             // TODO clearnet URLs, notify with HTTP_CLEARNET and/or HTTPS_CLEARNET
                             Map<UpdateMethod, List<URI>> sourceMap = new HashMap<UpdateMethod, List<URI>>(4);
                             // Must do su3 first
-                            if (ConfigUpdateHandler.USE_SU3_UPDATE) {
+                            //if (ConfigUpdateHandler.USE_SU3_UPDATE) {
                                 sourceMap.put(HTTP, _mgr.getUpdateURLs(ROUTER_SIGNED_SU3, "", HTTP));
                                 addMethod(TORRENT, args.get(SU3_KEY), sourceMap);
                                 addMethod(HTTP_CLEARNET, args.get(CLEARNET_HTTP_SU3_KEY), sourceMap);
@@ -261,14 +272,14 @@ class NewsFetcher extends UpdateRunner {
                                 _mgr.notifyVersionAvailable(this, _currentURI, ROUTER_SIGNED_SU3,
                                                             "", sourceMap, ver, "");
                                 sourceMap.clear();
-                            }
-                            // now do sud/su2
-                            sourceMap.put(HTTP, _mgr.getUpdateURLs(ROUTER_SIGNED, "", HTTP));
-                            String key = FileUtil.isPack200Supported() ? SU2_KEY : SUD_KEY;
-                            addMethod(TORRENT, args.get(key), sourceMap);
+                            //}
+                            // now do sud/su2 - DISABLED
+                            //sourceMap.put(HTTP, _mgr.getUpdateURLs(ROUTER_SIGNED, "", HTTP));
+                            //String key = FileUtil.isPack200Supported() ? SU2_KEY : SUD_KEY;
+                            //addMethod(TORRENT, args.get(key), sourceMap);
                             // notify about all sources at once
-                            _mgr.notifyVersionAvailable(this, _currentURI, ROUTER_SIGNED,
-                                                        "", sourceMap, ver, "");
+                            //_mgr.notifyVersionAvailable(this, _currentURI, ROUTER_SIGNED,
+                            //                            "", sourceMap, ver, "");
                         } else {
                             if (_log.shouldLog(Log.DEBUG))
                                 _log.debug("Our version is current");
@@ -415,9 +426,8 @@ class NewsFetcher extends UpdateRunner {
         
         if (_tempFile.exists() && _tempFile.length() > 0) {
             File from;
-            // TODO check magic number instead?
-            // But then a corrupt file would be displayed as-is...
-            if (url.endsWith(".su3") || url.contains(".su3?")) {
+            // sud/su2 disabled
+            //if (url.endsWith(".su3") || url.contains(".su3?")) {
                 try {
                     from = processSU3();
                 } catch (IOException ioe) {
@@ -425,9 +435,9 @@ class NewsFetcher extends UpdateRunner {
                     _tempFile.delete();
                     return;
                 }
-            } else {
-                from = _tempFile;
-            }
+            //} else {
+            //    from = _tempFile;
+            //}
             boolean copied = FileUtil.rename(from, _newsFile);
             _tempFile.delete();
             if (copied) {
@@ -579,9 +589,7 @@ class NewsFetcher extends UpdateRunner {
                 return;
             DateFormat fmt = DateFormat.getDateInstance(DateFormat.SHORT);
             // the router sets the JVM time zone to UTC but saves the original here so we can get it
-            String systemTimeZone = _context.getProperty("i2p.systemTimeZone");
-            if (systemTimeZone != null)
-                fmt.setTimeZone(TimeZone.getTimeZone(systemTimeZone));
+            fmt.setTimeZone(DataHelper.getSystemTimeZone(_context));
             for (NewsEntry e : entries) {
                 if (e.title == null || e.content == null)
                     continue;
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/CSSHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/CSSHelper.java
index 7870363b57fbfe195329db1572d6c4e426866abe..2218f06a7d3f89b78f1946bc70cdd09fdb23e3c1 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/CSSHelper.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/CSSHelper.java
@@ -107,7 +107,7 @@ public class CSSHelper extends HelperBase {
             if (Integer.parseInt(r) < MIN_REFRESH)
                 r = "" + MIN_REFRESH;
             _context.router().saveConfig(PROP_REFRESH, r);
-        } catch (Exception e) {
+        } catch (RuntimeException e) {
         }
     }
 
@@ -117,7 +117,7 @@ public class CSSHelper extends HelperBase {
         try {
             if (Integer.parseInt(r) < MIN_REFRESH)
                 r = "" + MIN_REFRESH;
-        } catch (Exception e) {
+        } catch (RuntimeException e) {
             r = "" + MIN_REFRESH;
         }
         return r;
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHelper.java
index c3abb58f540f80a7bc8b3b198821b9f30f4cd149..fbb94ddefcec1ae9ed0503e7e62ca95b02339601 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHelper.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHelper.java
@@ -207,7 +207,7 @@ public class ConfigNetHelper extends HelperBase {
             configs = Collections.emptySet();
         } else {
             configs = new HashSet<String>(4);
-            String[] ca = cs.split("[,; \r\n\t]");
+            String[] ca = DataHelper.split(cs, "[,; \r\n\t]");
             for (int i = 0; i < ca.length; i++) {
                 String c = ca[i];
                 if (c.length() > 0) {
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigReseedHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigReseedHandler.java
index f7d03555e040d7e77301f11de05ceab26ec0fc86..ab5e83408177504c6434746e179bddb036219d4e 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigReseedHandler.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigReseedHandler.java
@@ -2,8 +2,8 @@ package net.i2p.router.web;
 
 import java.io.InputStream;
 import java.io.IOException;
-import java.net.URL;
-import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -37,10 +37,10 @@ public class ConfigReseedHandler extends FormHandler {
                 addFormError(_t("You must enter a URL"));
                 return;
             }
-            URL url;
+            URI url;
             try {
-                url = new URL(val);
-            } catch (MalformedURLException mue) {
+                url = new URI(val);
+            } catch (URISyntaxException mue) {
                 addFormError(_t("Bad URL {0}", val));
                 return;
             }
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigSummaryHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigSummaryHandler.java
index 7747d22b2eb118551063d7e8b6a482d7e893bb81..1f75c3c82c64f8c6a6ecc1d7223568ce7a44486e 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigSummaryHandler.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigSummaryHandler.java
@@ -108,7 +108,7 @@ public class ConfigSummaryHandler extends FormHandler {
                     }
                 }
             } else if (moving) {
-                String parts[] = _action.split("_");
+                String parts[] = DataHelper.split(_action, "_");
                 try {
                     int from = Integer.parseInt(parts[1]);
                     int to = 0;
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/EventLogHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/EventLogHelper.java
index f42e1cd6e25d1a6fa7a7e9210e2b12143ff85813..01ac1614bbcb545f509d81f35ba22a3fd4720c86 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/EventLogHelper.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/EventLogHelper.java
@@ -12,7 +12,6 @@ import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.TimeZone;
 import java.util.TreeMap;
 
 import net.i2p.data.DataHelper;
@@ -189,9 +188,7 @@ public class EventLogHelper extends FormHandler {
 
         SimpleDateFormat fmt = (SimpleDateFormat) DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM);
         // the router sets the JVM time zone to UTC but saves the original here so we can get it
-        String systemTimeZone = _context.getProperty("i2p.systemTimeZone");
-        if (systemTimeZone != null)
-            fmt.setTimeZone(TimeZone.getTimeZone(systemTimeZone));
+        fmt.setTimeZone(DataHelper.getSystemTimeZone(_context));
 
         List<Map.Entry<Long, String>> entries = new ArrayList<Map.Entry<Long, String>>(events.entrySet());
         Collections.reverse(entries);
@@ -202,7 +199,7 @@ public class EventLogHelper extends FormHandler {
             buf.append(fmt.format(new Date(time)));
             buf.append("</td><td>");
             if (isAll) {
-                 String[] s = event.split(" ", 2);
+                 String[] s = DataHelper.split(event, " ", 2);
                  String xs = _xevents.get(s[0]);
                  if (xs == null)
                      xs = s[0];
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/HomeHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/HomeHelper.java
index b6afcf5cabd8926a059d2a275ef34b334add0a3c..f937acb27482751fe34f76eac2c4ef7f5b54eafa 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/HomeHelper.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/HomeHelper.java
@@ -135,8 +135,10 @@ public class HomeHelper extends HelperBase {
         return renderConfig(apps);
     }
 
+    private static final String SS = Character.toString(S);
+
     static Collection<App> buildApps(RouterContext ctx, String config) {
-        String[] args = config.split("" + S);
+        String[] args = DataHelper.split(config, SS);
         Set<App> apps = new TreeSet<App>(new AppComparator());
         for (int i = 0; i < args.length - 3; i += 4) {
             String name = Messages.getString(args[i], ctx);
@@ -149,7 +151,7 @@ public class HomeHelper extends HelperBase {
     }
 
     static Collection<App> buildSearchApps(String config) {
-        String[] args = config.split("" + S);
+        String[] args = DataHelper.split(config, SS);
         Set<App> apps = new TreeSet<App>(new AppComparator());
         for (int i = 0; i < args.length - 1; i += 2) {
             String name = args[i];
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/NewsFeedHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/NewsFeedHelper.java
index 357887f9e7e9d835cd9b0f24e046f09bf4abac8a..07dfa58546fe9967e6f3b612d9efaa5883842f99 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/NewsFeedHelper.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/NewsFeedHelper.java
@@ -4,7 +4,6 @@ import java.text.DateFormat;
 import java.util.Collections;
 import java.util.Date;
 import java.util.List;
-import java.util.TimeZone;
 
 import net.i2p.I2PAppContext;
 import net.i2p.app.ClientAppManager;
@@ -56,9 +55,7 @@ public class NewsFeedHelper extends HelperBase {
         if (!entries.isEmpty()) {
             DateFormat fmt = DateFormat.getDateInstance(DateFormat.SHORT);
             // the router sets the JVM time zone to UTC but saves the original here so we can get it
-            String systemTimeZone = ctx.getProperty("i2p.systemTimeZone");
-            if (systemTimeZone != null)
-                fmt.setTimeZone(TimeZone.getTimeZone(systemTimeZone));
+            fmt.setTimeZone(DataHelper.getSystemTimeZone(ctx));
             int i = 0;
             for (NewsEntry entry : entries) {
                 if (i++ < start)
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java b/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java
index 386eb664e1100555a511ea2e402d7a2da52fbdb3..2af0d5df46e56c33bcccf93d1afb0a4b3e6e6e9b 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java
@@ -423,7 +423,7 @@ public class PluginStarter implements Runnable {
                             addPath(f.toURI().toURL());
                             log.error("INFO: Adding translation plugin to classpath: " + f);
                             added = true;
-                        } catch (Exception e) {
+                        } catch (RuntimeException e) {
                             log.error("Plugin " + appName + " bad classpath element: " + f, e);
                         }
                     }
@@ -961,7 +961,7 @@ public class PluginStarter implements Runnable {
                 urls.add(f.toURI().toURL());
                 if (log.shouldLog(Log.WARN))
                     log.warn("INFO: Adding plugin to classpath: " + f);
-            } catch (Exception e) {
+            } catch (IOException e) {
                 log.error("Plugin client " + clientName + " bad classpath element: " + f, e);
             }
         }
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/SearchHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/SearchHelper.java
index e04809d6f3a338cc82e239285c8bfb02fae57045..b9e40f95968ea8816647688366ab57e07dfcf3fd 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/SearchHelper.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/SearchHelper.java
@@ -43,9 +43,11 @@ public class SearchHelper extends HelperBase {
         _query = s;
     }
 
+    private static final String SS = Character.toString(S);
+
     private void buildEngineMap() {
         String config = _context.getProperty(PROP_ENGINES, ENGINES_DEFAULT);
-        String[] args = config.split("" + S);
+        String[] args = DataHelper.split(config, SS);
         for (int i = 0; i < args.length - 1; i += 2) {
             String name = args[i];
             String url = args[i+1];
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/SummaryBarRenderer.java b/apps/routerconsole/java/src/net/i2p/router/web/SummaryBarRenderer.java
index 002f7983b0c38e3ef81c7aff16d2505406c3a2bc..710ce2753ddd483e2fc53514f61087bf3d6b95af 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/SummaryBarRenderer.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/SummaryBarRenderer.java
@@ -9,7 +9,6 @@ import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.TimeZone;
 
 import net.i2p.app.ClientAppManager;
 import net.i2p.crypto.SigType;
@@ -634,9 +633,7 @@ public class SummaryBarRenderer {
                 buf.append("<ul>\n");
                 DateFormat fmt = DateFormat.getDateInstance(DateFormat.SHORT);
                 // the router sets the JVM time zone to UTC but saves the original here so we can get it
-                String systemTimeZone = _context.getProperty("i2p.systemTimeZone");
-                if (systemTimeZone != null)
-                    fmt.setTimeZone(TimeZone.getTimeZone(systemTimeZone));
+                fmt.setTimeZone(DataHelper.getSystemTimeZone(_context));
                 int i = 0;
                 final int max = 2;
                 for (NewsEntry entry : entries) {
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java
index 7e97d9573947619b8a3b87920cacc930fe986b1c..75a55709fa03288818877c3f80238a50578d0039 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java
@@ -861,6 +861,8 @@ public class SummaryHelper extends HelperBase {
     public void storeNewsHelper(NewsHelper n) { _newshelper = n; }
     public NewsHelper getNewsHelper() { return _newshelper; }
 
+    private static final String SS = Character.toString(S);
+
     public List<String> getSummaryBarSections(String page) {
         String config = "";
         if ("home".equals(page)) {
@@ -870,7 +872,7 @@ public class SummaryHelper extends HelperBase {
             if (config == null)
                 config = _context.getProperty(PROP_SUMMARYBAR + "default", DEFAULT_FULL);
         }
-        return Arrays.asList(config.split("" + S));
+        return Arrays.asList(DataHelper.split(config, SS));
     }
 
     static void saveSummaryBarSections(RouterContext ctx, String page, Map<Integer, String> sections) {
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/UpdateHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/UpdateHandler.java
index e1e2edc538056dcccbd2caca75f8843b0803d8e9..de13a6112e6adfcf87f1fa067ed9d534e14449f8 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/UpdateHandler.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/UpdateHandler.java
@@ -84,7 +84,8 @@ public class UpdateHandler {
             } else if (ConfigUpdateHandler.USE_SU3_UPDATE) {
                 update(ROUTER_SIGNED_SU3);
             } else {
-                update(ROUTER_SIGNED);
+                // disabled, shouldn't get here
+                //update(ROUTER_SIGNED);
             }
         }
     }
diff --git a/apps/streaming/java/src/net/i2p/client/streaming/impl/I2PSocketManagerFull.java b/apps/streaming/java/src/net/i2p/client/streaming/impl/I2PSocketManagerFull.java
index 89310ebdae2ea5a3ed369ee89c32fbf082bfc0e8..7473bb79c646bc9a21a9edbbd297d58c6627393c 100644
--- a/apps/streaming/java/src/net/i2p/client/streaming/impl/I2PSocketManagerFull.java
+++ b/apps/streaming/java/src/net/i2p/client/streaming/impl/I2PSocketManagerFull.java
@@ -9,6 +9,7 @@ import java.net.NoRouteToHostException;
 import java.net.ServerSocket;
 import java.net.Socket;
 import java.net.SocketTimeoutException;
+import java.security.GeneralSecurityException;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -259,7 +260,13 @@ public class I2PSocketManagerFull implements I2PSocketManager {
                 Certificate.NULL_CERT.writeBytes(keyStream);
                 priv.writeBytes(keyStream);
                 keys[1].writeBytes(keyStream); // signing priv
-            } catch (Exception e) {
+            } catch (GeneralSecurityException e) {
+                throw new I2PSessionException("Error creating keys", e);
+            } catch (I2PException e) {
+                throw new I2PSessionException("Error creating keys", e);
+            } catch (IOException e) {
+                throw new I2PSessionException("Error creating keys", e);
+            } catch (RuntimeException e) {
                 throw new I2PSessionException("Error creating keys", e);
             }
             privateKeyStream = new ByteArrayInputStream(keyStream.toByteArray());
diff --git a/apps/streaming/java/src/net/i2p/client/streaming/impl/Packet.java b/apps/streaming/java/src/net/i2p/client/streaming/impl/Packet.java
index a872b2d77d0d7ccd534bcb2d218210cde985db07..a887dc3d2ba54496d91459733c3faf9a893ee816 100644
--- a/apps/streaming/java/src/net/i2p/client/streaming/impl/Packet.java
+++ b/apps/streaming/java/src/net/i2p/client/streaming/impl/Packet.java
@@ -585,7 +585,7 @@ class Packet {
         cur += 4;
         setAckThrough(DataHelper.fromLong(buffer, cur, 4));
         cur += 4;
-        int numNacks = (int)DataHelper.fromLong(buffer, cur, 1);
+        int numNacks = buffer[cur] & 0xff;
         cur++;
         if (length < 22 + numNacks*4)
             throw new IllegalArgumentException("Too small with " + numNacks + " nacks: " + length);
@@ -599,7 +599,7 @@ class Packet {
         } else {
             setNacks(null);
         }
-        setResendDelay((int)DataHelper.fromLong(buffer, cur, 1));
+        setResendDelay(buffer[cur] & 0xff);
         cur++;
         setFlags((int)DataHelper.fromLong(buffer, cur, 2));
         cur += 2;
diff --git a/apps/susidns/src/java/src/i2p/susi/dns/AddressbookBean.java b/apps/susidns/src/java/src/i2p/susi/dns/AddressbookBean.java
index 360310682a9f4845c4107bafee031127f4c6a931..205fcc8a75cd519d38a25f9403ade1ca9f450a6f 100644
--- a/apps/susidns/src/java/src/i2p/susi/dns/AddressbookBean.java
+++ b/apps/susidns/src/java/src/i2p/susi/dns/AddressbookBean.java
@@ -164,7 +164,7 @@ public class AddressbookBean extends BaseBean
 
 			message = generateLoadMessage();
 		}
-		catch (Exception e) {
+		catch (IOException e) {
 			warn(e);
 		} finally {
 			if (fis != null)
@@ -316,7 +316,7 @@ public class AddressbookBean extends BaseBean
 					try {
 						save();
 						message += "<br>" + _t("Address book saved.");
-					} catch (Exception e) {
+					} catch (IOException e) {
 						warn(e);
 						message += "<br>" + _t("ERROR: Could not write addressbook file.");
 					}
diff --git a/apps/susidns/src/java/src/i2p/susi/dns/FormatDate.java b/apps/susidns/src/java/src/i2p/susi/dns/FormatDate.java
index ad576dfb04fdc3360d221c89d3ee81ba32f8666e..c7604a213fff50bcb6efa34dd5f440c7f5eb37e1 100644
--- a/apps/susidns/src/java/src/i2p/susi/dns/FormatDate.java
+++ b/apps/susidns/src/java/src/i2p/susi/dns/FormatDate.java
@@ -2,9 +2,9 @@ package i2p.susi.dns;
 
 import java.util.Date;
 import java.text.DateFormat;
-import java.util.TimeZone;
 
 import net.i2p.I2PAppContext;
+import net.i2p.data.DataHelper;
 
 /**
  * Format a date in local time zone
@@ -17,9 +17,7 @@ public abstract class FormatDate
     static {
 	DateFormat fmt = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM);
 	// the router sets the JVM time zone to UTC but saves the original here so we can get it
-	String systemTimeZone = I2PAppContext.getGlobalContext().getProperty("i2p.systemTimeZone");
-	if (systemTimeZone != null)
-		fmt.setTimeZone(TimeZone.getTimeZone(systemTimeZone));
+        fmt.setTimeZone(DataHelper.getSystemTimeZone(I2PAppContext.getGlobalContext()));
 	_dateFormat = fmt;
     }
 
diff --git a/apps/susidns/src/java/src/i2p/susi/dns/NamingServiceBean.java b/apps/susidns/src/java/src/i2p/susi/dns/NamingServiceBean.java
index 14eaebbd732779766e6fc08aa68573c907b34685..0fd3262019eebd93979fb5c0f24e0497553027b1 100644
--- a/apps/susidns/src/java/src/i2p/susi/dns/NamingServiceBean.java
+++ b/apps/susidns/src/java/src/i2p/susi/dns/NamingServiceBean.java
@@ -205,7 +205,7 @@ public class NamingServiceBean extends AddressbookBean
 
 			message = generateLoadMessage();
 		}
-		catch (Exception e) {
+		catch (RuntimeException e) {
 			warn(e);
 		}
 		if( message.length() > 0 )
diff --git a/apps/susimail/src/src/i2p/susi/util/Config.java b/apps/susimail/src/src/i2p/susi/util/Config.java
index a84ccede473ab1bde9d2ffcd6d38cbbc44224f46..26171e87f2122078d7c358324a5eb4204b96edc3 100644
--- a/apps/susimail/src/src/i2p/susi/util/Config.java
+++ b/apps/susimail/src/src/i2p/susi/util/Config.java
@@ -98,7 +98,7 @@ public class Config {
 		try {
 			iv = Config.class.getResourceAsStream("/susimail.properties");
 			properties.load(iv);
-		} catch (Exception e) {
+		} catch (IOException e) {
 			Debug.debug(Debug.ERROR, "Could not open WEB-INF/classes/susimail.properties (possibly in jar), reason: " + e);
 		} finally {
 			if(iv != null) try { iv.close(); } catch(IOException ioe) {}
@@ -109,7 +109,7 @@ public class Config {
 				config = new OrderedProperties();
 				DataHelper.loadProps(config, cfg);
 			}
-		} catch (Exception e) {
+		} catch (IOException e) {
 			Debug.debug(Debug.ERROR, "Could not open susimail.config, reason: " + e);
 		}
 	}
diff --git a/apps/susimail/src/src/i2p/susi/webmail/Mail.java b/apps/susimail/src/src/i2p/susi/webmail/Mail.java
index 8632ac9c419a5e4bb86d86cd6241599d4ce4461e..c184831c3419b74d3b8616b581cb20c977ee96a8 100644
--- a/apps/susimail/src/src/i2p/susi/webmail/Mail.java
+++ b/apps/susimail/src/src/i2p/susi/webmail/Mail.java
@@ -42,6 +42,7 @@ import java.util.Locale;
 import java.util.TimeZone;
 
 import net.i2p.I2PAppContext;
+import net.i2p.data.DataHelper;
 
 /**
  * data structure to hold a single message, mostly used with folder view and sorting
@@ -126,7 +127,7 @@ class Mail {
 			part = new MailPart(rb);
 		} catch (DecodingException de) {
 			Debug.debug(Debug.ERROR, "Decode error: " + de);
-		} catch (Exception e) {
+		} catch (RuntimeException e) {
 			Debug.debug(Debug.ERROR, "Parse error: " + e);
 		}
 	}
@@ -190,7 +191,7 @@ class Mail {
 				address.indexOf( "\r" ) != -1 )
 			return false;
 		
-		String[] tokens = address.split( "[ \t]+" );
+		String[] tokens = DataHelper.split(address, "[ \t]+");
 
 		int addresses = 0;
 		
@@ -208,7 +209,7 @@ class Mail {
 	 */
 	public static String getAddress(String address )
 	{
-		String[] tokens = address.split( "[ \t]+" );
+		String[] tokens = DataHelper.split(address, "[ \t]+");
 
 		for( int i = 0; i < tokens.length; i++ ) {
 			if( tokens[i].matches( "^[^@< \t]+@[^> \t]+$" ) )
@@ -232,7 +233,7 @@ class Mail {
 	public static boolean getRecipientsFromList( ArrayList<String> recipients, String text, boolean ok )
 	{
 		if( text != null && text.length() > 0 ) {			
-			String[] ccs = text.split( "," );
+			String[] ccs = DataHelper.split(text, ",");
 			for( int i = 0; i < ccs.length; i++ ) {
 				String recipient = ccs[i].trim();
 				if( validateAddress( recipient ) ) {
@@ -275,12 +276,9 @@ class Mail {
 		DateFormat localDateFormatter = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
 		DateFormat longLocalDateFormatter = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM);
 		// the router sets the JVM time zone to UTC but saves the original here so we can get it
-		String systemTimeZone = I2PAppContext.getGlobalContext().getProperty("i2p.systemTimeZone");
-		if (systemTimeZone != null) {
-			TimeZone tz = TimeZone.getTimeZone(systemTimeZone);
-			localDateFormatter.setTimeZone(tz);
-			longLocalDateFormatter.setTimeZone(tz);
-		}
+		TimeZone tz = DataHelper.getSystemTimeZone(I2PAppContext.getGlobalContext());
+		localDateFormatter.setTimeZone(tz);
+		longLocalDateFormatter.setTimeZone(tz);
 		DateFormat mailDateFormatter = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z", Locale.ENGLISH );
 		
 		error = "";
diff --git a/apps/susimail/src/src/i2p/susi/webmail/MailPart.java b/apps/susimail/src/src/i2p/susi/webmail/MailPart.java
index 0ec32abe9361154ab124710a85815fb291271123..ff348a502a61e565a3e41282de6c705cb5989471 100644
--- a/apps/susimail/src/src/i2p/susi/webmail/MailPart.java
+++ b/apps/susimail/src/src/i2p/susi/webmail/MailPart.java
@@ -79,7 +79,7 @@ class MailPart {
 		beginBody = bb;
 			
 		ReadBuffer decodedHeaders = EncodingFactory.getEncoding( "HEADERLINE" ).decode( buffer.content, begin, beginBody - begin );
-		headerLines = new String( decodedHeaders.content, decodedHeaders.offset, decodedHeaders.length ).split( "\r\n" );
+		headerLines = DataHelper.split(new String(decodedHeaders.content, decodedHeaders.offset, decodedHeaders.length), "\r\n");
 
 		String boundary = null;
 		String x_encoding = null;
diff --git a/apps/susimail/src/src/i2p/susi/webmail/WebMail.java b/apps/susimail/src/src/i2p/susi/webmail/WebMail.java
index 9ff4b933b74cff3016131aba49029f1d7e97568d..bdcab5e89d9b3857512f37a4f7c5bc09160eac88 100644
--- a/apps/susimail/src/src/i2p/susi/webmail/WebMail.java
+++ b/apps/susimail/src/src/i2p/susi/webmail/WebMail.java
@@ -614,7 +614,7 @@ public class WebMail extends HttpServlet
 						showBody = false;
 						reason = _t("Charset \\''{0}\\'' not supported.", quoteHTML( mailPart.charset )) + br;
 					}
-					catch (Exception e1) {
+					catch (IOException e1) {
 						showBody = false;
 						reason += _t("Part ({0}) not shown, because of {1}", ident, e1.toString()) + br;
 					}
@@ -996,7 +996,7 @@ public class WebMail extends HttpServlet
 							PrintWriter pw2 = new PrintWriter( text2 );
 							showPart( pw2, part, 0, TEXT_ONLY );
 							pw2.flush();
-							String[] lines = text2.toString().split( "\r\n" );
+							String[] lines = DataHelper.split(text2.toString(), "\r\n");
 							for( int i = 0; i < lines.length; i++ )
 								pw.println( "> " + lines[i] );
 							pw.flush();
diff --git a/apps/susimail/src/src/i2p/susi/webmail/encoding/DecodingException.java b/apps/susimail/src/src/i2p/susi/webmail/encoding/DecodingException.java
index d0d89dd00f302942c10955bcc4e0791253e21c14..46277d7e5517788e1ae9cdf01e00501f74b60585 100644
--- a/apps/susimail/src/src/i2p/susi/webmail/encoding/DecodingException.java
+++ b/apps/susimail/src/src/i2p/susi/webmail/encoding/DecodingException.java
@@ -23,10 +23,12 @@
  */
 package i2p.susi.webmail.encoding;
 
+import java.io.IOException;
+
 /**
  * @author susi
  */
-public class DecodingException extends Exception {
+public class DecodingException extends IOException {
 	private static final long serialVersionUID = 1L;
 
 	public DecodingException( String msg ) {
diff --git a/apps/susimail/src/src/i2p/susi/webmail/encoding/HeaderLine.java b/apps/susimail/src/src/i2p/susi/webmail/encoding/HeaderLine.java
index 4ed370dd2ee3672e0e9a8461dac9aed328a25235..e7db3672dcf8720341db5108ccdc3a5e905dbc5b 100644
--- a/apps/susimail/src/src/i2p/susi/webmail/encoding/HeaderLine.java
+++ b/apps/susimail/src/src/i2p/susi/webmail/encoding/HeaderLine.java
@@ -233,7 +233,9 @@ public class HeaderLine implements Encoding {
 												length -= distance;
 												lastCharWasQuoted = true;
 												continue;
-											} catch (Exception e1) {
+											} catch (IOException e1) {
+												Debug.debug(Debug.ERROR, e1.toString());
+											} catch (RuntimeException e1) {
 												Debug.debug(Debug.ERROR, e1.toString());
 											}
 										}
diff --git a/apps/susimail/src/src/i2p/susi/webmail/smtp/SMTPClient.java b/apps/susimail/src/src/i2p/susi/webmail/smtp/SMTPClient.java
index 046dcbef5288142efd9618931de465fc312861cf..fc47577351e5cadf9866c281f7e02acdec9f1e21 100644
--- a/apps/susimail/src/src/i2p/susi/webmail/smtp/SMTPClient.java
+++ b/apps/susimail/src/src/i2p/susi/webmail/smtp/SMTPClient.java
@@ -210,7 +210,7 @@ public class SMTPClient {
 		
 		try {
 			socket = new Socket( host, port );
-		} catch (Exception e) {
+		} catch (IOException e) {
 			error += _t("Cannot connect") + ": " + e.getMessage() + '\n';
 			ok = false;
 		}
@@ -282,7 +282,7 @@ public class SMTPClient {
 			error += e.getMessage();
 		}
 		if( !mailSent && lastResponse.length() > 0 ) {
-			String[] lines = lastResponse.split( "\r" );
+			String[] lines = DataHelper.split(lastResponse, "\r");
 			for( int i = 0; i < lines.length; i++ )
 				error += lines[i] + '\n';			
 		}
diff --git a/apps/systray/java/build.xml b/apps/systray/java/build.xml
index 9fccf66fcded6899cdc05bc371001017edeface6..c5eb70731cea16479deb71ca894ac1aea62e0a0c 100644
--- a/apps/systray/java/build.xml
+++ b/apps/systray/java/build.xml
@@ -5,11 +5,26 @@
     <target name="builddep">
         <!-- run from top level build.xml to get dependencies built -->
     </target>
+    <condition property="depend.available">
+        <typefound name="depend" />
+    </condition>
+    <target name="depend" if="depend.available">
+        <depend
+            cache="../../../build"
+            srcdir="./src:./test/junit"
+            destdir="./build/obj" >
+            <!-- Depend on classes instead of jars where available -->
+            <classpath>
+                <pathelement location="../../../core/java/build/obj" />
+                <pathelement location="lib/systray4j.jar" />
+            </classpath>
+        </depend>
+    </target>
 
     <property name="javac.compilerargs" value="" />
     <property name="javac.version" value="1.6" />
 
-    <target name="compile">
+    <target name="compile" depends="depend">
         <mkdir dir="./build" />
         <mkdir dir="./build/obj" />
         <javac
diff --git a/apps/systray/java/src/net/i2p/apps/systray/ConfigFile.java b/apps/systray/java/src/net/i2p/apps/systray/ConfigFile.java
index 642bea630a129b687532e6d129d2f90a23d3d7cf..0389c8dd76c02eba23a4e4f8f7c2b36043bf1527 100644
--- a/apps/systray/java/src/net/i2p/apps/systray/ConfigFile.java
+++ b/apps/systray/java/src/net/i2p/apps/systray/ConfigFile.java
@@ -62,7 +62,7 @@ public class ConfigFile {
         try {
             fileInputStream = new FileInputStream(_configFile);
             _properties.load(fileInputStream);
-        } catch (Exception e) {
+        } catch (IOException e) {
             rv = false;
         } finally {
             if (fileInputStream != null) {
@@ -79,7 +79,7 @@ public class ConfigFile {
         try {
             fileOutputStream = new FileOutputStream(_configFile);
             _properties.store(fileOutputStream, null);
-        } catch (Exception e) {
+        } catch (IOException e) {
             rv = false;
         } finally {
             if (fileOutputStream != null) {
diff --git a/apps/systray/java/src/net/i2p/apps/systray/SysTray.java b/apps/systray/java/src/net/i2p/apps/systray/SysTray.java
index 8d030d09171d48309f955332140b7dbc7cba99ce..da7f6c37b4b1c9b73344262566b206374897edc8 100644
--- a/apps/systray/java/src/net/i2p/apps/systray/SysTray.java
+++ b/apps/systray/java/src/net/i2p/apps/systray/SysTray.java
@@ -11,6 +11,7 @@ package net.i2p.apps.systray;
 
 import java.awt.Frame;
 import java.io.File;
+import java.io.IOException;
 
 import net.i2p.I2PAppContext;
 import net.i2p.util.SimpleTimer;
@@ -88,14 +89,14 @@ public class SysTray implements SysTrayMenuListener {
             try {
                 if (urlLauncher.openUrl(url))
                     return;
-            } catch (Exception ex) {
+            } catch (IOException ex) {
                 // Fall through.
             }
         } else {
             try {
                 if (urlLauncher.openUrl(url, _browserString))
                     return;
-            } catch (Exception ex) {
+            } catch (IOException ex) {
                 // Fall through.
             }
         }
diff --git a/apps/systray/java/src/net/i2p/apps/systray/UrlLauncher.java b/apps/systray/java/src/net/i2p/apps/systray/UrlLauncher.java
index 7604169b00fcafcdac878de58593ae92ce4c37a6..aa8f5ef1257ec954209065338d839b037fc1af8b 100644
--- a/apps/systray/java/src/net/i2p/apps/systray/UrlLauncher.java
+++ b/apps/systray/java/src/net/i2p/apps/systray/UrlLauncher.java
@@ -18,7 +18,8 @@ import java.io.InputStreamReader;
 import java.net.MalformedURLException;
 import java.net.Socket;
 import java.net.SocketAddress;
-import java.net.URL;
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.util.Locale;
 
 import net.i2p.I2PAppContext;
@@ -115,18 +116,16 @@ public class UrlLauncher implements ClientApp {
      *  @return success
      */
     private static boolean waitForServer(String urlString) {
-        URL url;
+        URI url;
         try {
-            url = new URL(urlString);
-        } catch (MalformedURLException e) {
+            url = new URI(urlString);
+        } catch (URISyntaxException e) {
             return false;
         }
         String host = url.getHost();
         int port = url.getPort();
         if (port <= 0) {
-            port = url.getDefaultPort();
-            if (port <= 0)
-                return false;
+            port = "https".equals(url.getScheme()) ? 443 : 80;
         }
         SocketAddress sa;
         try {
@@ -150,7 +149,7 @@ public class UrlLauncher implements ClientApp {
                    Thread.sleep(2*1000);
                 } catch (InterruptedException ie) {}
                 return true;
-            } catch (Exception e) {}
+            } catch (IOException e) {}
             if (System.currentTimeMillis() > done)
                 break;
             try {
@@ -172,9 +171,9 @@ public class UrlLauncher implements ClientApp {
      * @return     <code>true</code> if the operation was successful, otherwise
      *             <code>false</code>.
      * 
-     * @throws Exception
+     * @throws IOException
      */ 
-    public boolean openUrl(String url) throws Exception {
+    public boolean openUrl(String url) throws IOException {
         waitForServer(url);
         if (validateUrlFormat(url)) {
             String cbrowser = _context.getProperty(PROP_BROWSER);
@@ -218,7 +217,7 @@ public class UrlLauncher implements ClientApp {
                         // No worries.
                     }
                     foo.delete();
-                } catch (Exception e) {
+                } catch (IOException e) {
                     // Defaults to IE.
                 } finally {
                     if (bufferedReader != null)
@@ -247,9 +246,9 @@ public class UrlLauncher implements ClientApp {
      * @return         <code>true</code> if the operation was successful,
      *                 otherwise <code>false</code>.
      * 
-     * @throws Exception
+     * @throws IOException
      */
-    public boolean openUrl(String url, String browser) throws Exception {
+    public boolean openUrl(String url, String browser) throws IOException {
         waitForServer(url);
         if (validateUrlFormat(url)) {
             if (_shellCommand.executeSilentAndWaitTimed(browser + " " + url, 5))
@@ -261,8 +260,8 @@ public class UrlLauncher implements ClientApp {
     private static boolean validateUrlFormat(String urlString) {
          try {
             // just to check validity
-            new URL(urlString);
-        } catch (MalformedURLException e) {
+            new URI(urlString);
+        } catch (URISyntaxException e) {
             return false;
         }
         return true;
@@ -290,7 +289,7 @@ public class UrlLauncher implements ClientApp {
                 String url = _args[0];
                 openUrl(url);
                 changeState(STOPPED);
-            } catch (Exception e) {
+            } catch (IOException e) {
                 changeState(CRASHED, e);
             }
         }
@@ -355,6 +354,6 @@ public class UrlLauncher implements ClientApp {
                 launcher.openUrl(args[0]);
             else
                 launcher.openUrl("http://127.0.0.1:7657/index.jsp");
-         } catch (Exception e) {}
+         } catch (IOException e) {}
     }
 }
diff --git a/build.xml b/build.xml
index 9544491b5dff132b89182351e317ca7952e1a205..1963a9d91223a2cde1a0d2632390d01c9566590f 100644
--- a/build.xml
+++ b/build.xml
@@ -868,6 +868,21 @@
     </target>
 
     <target name="-pre-release">
+            <fail message="javac.compilerargs must contain a -bootclasspath option in override.properties">
+                <condition>
+                    <not><contains string="${javac.compilerargs}" substring="-bootclasspath"/></not>
+                </condition>
+            </fail>
+            <fail message="build.built-by must be set in override.properties">
+                <condition>
+                    <equals arg1="${build.built-by}" arg2="unknown"/>
+                </condition>
+            </fail>
+            <fail message="require.gettext must be true">
+                <condition>
+                    <not><equals arg1="${require.gettext}" arg2="true"/></not>
+                </condition>
+            </fail>
             <echo message="================================================================" />
             <echo message="Did you update these files?" />
             <exec executable="ls" failonerror="true">
@@ -1450,9 +1465,7 @@
     </target>
 
     <!-- All jetty jars required for update.
-         We don't need commons-el or commons-logging, they haven't changed.
          TODO do we need to bother updating jasper?
-         TODO where is JMX? We don't need it I hope.
       -->
     <target name="prepjupdate" depends="prepupdate, buildWEB">
         <copy todir="pkg-temp/lib" >
diff --git a/core/java/src/net/i2p/client/impl/I2PSessionImpl.java b/core/java/src/net/i2p/client/impl/I2PSessionImpl.java
index 046683ea408ad2a9e5f9831191edbc7c7ab9cd83..13d805619adaf9793303e9beaf68ac0eece91621 100644
--- a/core/java/src/net/i2p/client/impl/I2PSessionImpl.java
+++ b/core/java/src/net/i2p/client/impl/I2PSessionImpl.java
@@ -862,7 +862,7 @@ public abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2
                             if ((duration > 100) && _log.shouldLog(Log.INFO)) 
                                 _log.info("Message availability notification for " + msgId.intValue() + " took " 
                                            + duration + " to " + _sessionListener);
-                        } catch (Exception e) {
+                        } catch (RuntimeException e) {
                             _log.log(Log.CRIT, "Error notifying app of message availability", e);
                         }
                     } else {
diff --git a/core/java/src/net/i2p/client/impl/I2PSessionMuxedImpl.java b/core/java/src/net/i2p/client/impl/I2PSessionMuxedImpl.java
index 07003ae2d2aba35f616182bd26eb3996b78cd1a7..9b6f1ff0c8bd170bdbde3d8f4bd01c6dc72c51ab 100644
--- a/core/java/src/net/i2p/client/impl/I2PSessionMuxedImpl.java
+++ b/core/java/src/net/i2p/client/impl/I2PSessionMuxedImpl.java
@@ -399,7 +399,7 @@ class I2PSessionMuxedImpl extends I2PSessionImpl2 {
                 try {
                     _demultiplexer.messageAvailable(I2PSessionMuxedImpl.this,
                         msg.id, msg.size, msg.proto, msg.fromPort, msg.toPort);
-                } catch (Exception e) {
+                } catch (RuntimeException e) {
                     _log.error("Error notifying app of message availability", e);
                 }
             }
diff --git a/core/java/src/net/i2p/client/naming/SingleFileNamingService.java b/core/java/src/net/i2p/client/naming/SingleFileNamingService.java
index 219c61bbc2cc29bc24b0882029bedb8031ebc219..38a174b7e727970e6ce0f217846a281d033fbc57 100644
--- a/core/java/src/net/i2p/client/naming/SingleFileNamingService.java
+++ b/core/java/src/net/i2p/client/naming/SingleFileNamingService.java
@@ -91,7 +91,7 @@ public class SingleFileNamingService extends NamingService {
                 key = getKey(hostname.substring(4));
             if (key != null)
                 return lookupBase64(key);
-        } catch (Exception ioe) {
+        } catch (IOException ioe) {
             if (_file.exists())
                 _log.error("Error loading hosts file " + _file, ioe);
             else if (_log.shouldLog(Log.WARN))
@@ -123,7 +123,7 @@ public class SingleFileNamingService extends NamingService {
                     return line.substring(0, split);
             }
             return null;
-        } catch (Exception ioe) {
+        } catch (IOException ioe) {
             if (_file.exists())
                 _log.error("Error loading hosts file " + _file, ioe);
             else if (_log.shouldLog(Log.WARN))
diff --git a/core/java/src/net/i2p/crypto/DSAEngine.java b/core/java/src/net/i2p/crypto/DSAEngine.java
index d7dfe657a188f88ebb4fd9af5b1fb3f4c83cd08b..76f2004f8b01c1c9ef0777531f0505102d2ea1eb 100644
--- a/core/java/src/net/i2p/crypto/DSAEngine.java
+++ b/core/java/src/net/i2p/crypto/DSAEngine.java
@@ -257,7 +257,7 @@ public class DSAEngine {
                     _log.warn("Took too long to verify the signature (" + diff + "ms)");
             }
             return ok;
-        } catch (Exception e) {
+        } catch (RuntimeException e) {
             _log.log(Log.CRIT, "Error verifying the signature", e);
             return false;
         }
diff --git a/core/java/src/net/i2p/crypto/ECConstants.java b/core/java/src/net/i2p/crypto/ECConstants.java
index d9b111e234e26df3235c6fe1146854913ccbadaa..8bca0b0ac04ce46de69afe762eefc0d15d565b8f 100644
--- a/core/java/src/net/i2p/crypto/ECConstants.java
+++ b/core/java/src/net/i2p/crypto/ECConstants.java
@@ -3,6 +3,7 @@ package net.i2p.crypto;
 import java.lang.reflect.Constructor;
 import java.math.BigInteger;
 import java.security.AlgorithmParameters;
+import java.security.GeneralSecurityException;
 import java.security.Provider;
 import java.security.Security;
 import java.security.spec.ECField;
@@ -278,7 +279,7 @@ class ECConstants {
             AlgorithmParameters ap;
             try {
                 ap = AlgorithmParameters.getInstance("EC");
-            } catch (Exception e) {
+            } catch (GeneralSecurityException e) {
                 if (BC_AVAILABLE) {
                     log("Named curve " + name + " is not available, trying BC", e);
                     ap = AlgorithmParameters.getInstance("EC", "BC");
@@ -292,7 +293,7 @@ class ECConstants {
             ECParameterSpec rv = ap.getParameterSpec(ECParameterSpec.class);
             log("Named curve " + name + " loaded");
             return rv;
-        } catch (Exception e) {
+        } catch (GeneralSecurityException e) {
             log("Named curve " + name + " is not available", e);
             return null;
         }
diff --git a/core/java/src/net/i2p/crypto/ElGamalAESEngine.java b/core/java/src/net/i2p/crypto/ElGamalAESEngine.java
index 126a06a36f08a098ac20a37504de49cdd93ba4ed..0fe652bb219305fc889a8b40358fe29d5d32f270 100644
--- a/core/java/src/net/i2p/crypto/ElGamalAESEngine.java
+++ b/core/java/src/net/i2p/crypto/ElGamalAESEngine.java
@@ -327,12 +327,12 @@ public class ElGamalAESEngine {
             //ByteArrayInputStream bais = new ByteArrayInputStream(decrypted);
             int cur = 0;
             long numTags = DataHelper.fromLong(decrypted, cur, 2);
-            if ((numTags < 0) || (numTags > MAX_TAGS_RECEIVED)) throw new Exception("Invalid number of session tags");
+            if ((numTags < 0) || (numTags > MAX_TAGS_RECEIVED)) throw new IllegalArgumentException("Invalid number of session tags");
             if (numTags > 0) tags = new ArrayList<SessionTag>((int)numTags);
             cur += 2;
             //_log.debug("# tags: " + numTags);
             if (numTags * SessionTag.BYTE_LENGTH > decrypted.length - 2) {
-                throw new Exception("# tags: " + numTags + " is too many for " + (decrypted.length - 2));
+                throw new IllegalArgumentException("# tags: " + numTags + " is too many for " + (decrypted.length - 2));
             }
             for (int i = 0; i < numTags; i++) {
                 byte tag[] = new byte[SessionTag.BYTE_LENGTH];
@@ -344,7 +344,7 @@ public class ElGamalAESEngine {
             cur += 4;
             //_log.debug("len: " + len);
             if ((len < 0) || (len > decrypted.length - cur - Hash.HASH_LENGTH - 1)) 
-                throw new Exception("Invalid size of payload (" + len + ", remaining " + (decrypted.length-cur) +")");
+                throw new IllegalArgumentException("Invalid size of payload (" + len + ", remaining " + (decrypted.length-cur) +")");
             //byte hashval[] = new byte[Hash.HASH_LENGTH];
             //System.arraycopy(decrypted, cur, hashval, 0, Hash.HASH_LENGTH);
             //readHash = new Hash();
@@ -379,8 +379,8 @@ public class ElGamalAESEngine {
                 return unencrData;
             }
 
-            throw new Exception("Hash does not match");
-        } catch (Exception e) {
+            throw new RuntimeException("Hash does not match");
+        } catch (RuntimeException e) {
             if (_log.shouldLog(Log.WARN)) _log.warn("Unable to decrypt AES block", e);
             return null;
         }
diff --git a/core/java/src/net/i2p/crypto/EncType.java b/core/java/src/net/i2p/crypto/EncType.java
index cb9c0ad9632432ae068fe9b0566825c1ecc8a361..fc07d5d5a1a5d06725acea3f719e659ef47d3ec6 100644
--- a/core/java/src/net/i2p/crypto/EncType.java
+++ b/core/java/src/net/i2p/crypto/EncType.java
@@ -108,7 +108,7 @@ public enum EncType {
             return true;
         try {
             getParams();
-        } catch (Exception e) {
+        } catch (InvalidParameterSpecException e) {
             return false;
         }
         return true;
diff --git a/core/java/src/net/i2p/crypto/KeyGenerator.java b/core/java/src/net/i2p/crypto/KeyGenerator.java
index 60742c2fb9682731d0ebab6be19ddb9dbb015ed3..c198d0b34b06dace62ea9e8e8f114596bc2c0aa2 100644
--- a/core/java/src/net/i2p/crypto/KeyGenerator.java
+++ b/core/java/src/net/i2p/crypto/KeyGenerator.java
@@ -343,7 +343,7 @@ public class KeyGenerator {
     public static void main(String args[]) {
         try {
              main2(args);
-        } catch (Exception e) {
+        } catch (RuntimeException e) {
              e.printStackTrace();
         }
     }
@@ -381,7 +381,7 @@ public class KeyGenerator {
                 try {
                     System.out.println("Testing " + type);
                     testSig(type, runs);
-                } catch (Exception e) {
+                } catch (GeneralSecurityException e) {
                     System.out.println("error testing " + type);
                     e.printStackTrace();
                 }
diff --git a/core/java/src/net/i2p/crypto/KeyStoreUtil.java b/core/java/src/net/i2p/crypto/KeyStoreUtil.java
index 06b73cea81cbc8832269dc4069a2fc72868e1a5e..d994018cf912f84fd5a98536c6db302b80a0feba 100644
--- a/core/java/src/net/i2p/crypto/KeyStoreUtil.java
+++ b/core/java/src/net/i2p/crypto/KeyStoreUtil.java
@@ -98,7 +98,8 @@ public class KeyStoreUtil {
                     try {
                         ks.load(null, DEFAULT_KEYSTORE_PASSWORD.toCharArray());
                         success = addCerts(new File(System.getProperty("java.home"), "etc/security/cacerts"), ks) > 0;
-                    } catch (Exception e) {}
+                    } catch (IOException e) {
+                    } catch (GeneralSecurityException e) {}
                 } else {
                     success = loadCerts(new File(System.getProperty("java.home"), "etc/security/cacerts.bks"), ks);
                 }
@@ -113,7 +114,8 @@ public class KeyStoreUtil {
             try {
                 // must be initted
                 ks.load(null, DEFAULT_KEYSTORE_PASSWORD.toCharArray());
-            } catch (Exception e) {}
+            } catch (IOException e) {
+            } catch (GeneralSecurityException e) {}
             error("All key store loads failed, will only load local certificates", null);
         }
         return ks;
@@ -140,13 +142,15 @@ public class KeyStoreUtil {
             try {
                 // not clear if null is allowed for password
                 ks.load(null, DEFAULT_KEYSTORE_PASSWORD.toCharArray());
-            } catch (Exception foo) {}
+            } catch (IOException foo) {
+            } catch (GeneralSecurityException e) {}
             return false;
         } catch (IOException ioe) {
             error("KeyStore load error, no default keys: " + file.getAbsolutePath(), ioe);
             try {
                 ks.load(null, DEFAULT_KEYSTORE_PASSWORD.toCharArray());
-            } catch (Exception foo) {}
+            } catch (IOException foo) {
+            } catch (GeneralSecurityException e) {}
             return false;
         } finally {
             try { if (fis != null) fis.close(); } catch (IOException foo) {}
@@ -171,7 +175,7 @@ public class KeyStoreUtil {
                     count++;
                 }
             }
-        } catch (Exception foo) {}
+        } catch (GeneralSecurityException e) {}
         return count;
     }
 
@@ -316,7 +320,10 @@ public class KeyStoreUtil {
                     error("Not overwriting key " + alias + ", already exists in " + ks, null);
                     return false;
                 }
-            } catch (Exception e) {
+            } catch (IOException e) {
+                error("Not overwriting key \"" + alias + "\", already exists in " + ks, e);
+                return false;
+            } catch (GeneralSecurityException e) {
                 error("Not overwriting key \"" + alias + "\", already exists in " + ks, e);
                 return false;
             }
@@ -354,7 +361,10 @@ public class KeyStoreUtil {
                     success = getPrivateKey(ks, ksPW, alias, keyPW) != null;
                     if (!success)
                         error("Key gen failed to get private key", null);
-                } catch (Exception e) {
+                } catch (IOException e) {
+                    error("Key gen failed to get private key", e);
+                    success = false;
+                } catch (GeneralSecurityException e) {
                     error("Key gen failed to get private key", e);
                     success = false;
                 }
diff --git a/core/java/src/net/i2p/crypto/SigType.java b/core/java/src/net/i2p/crypto/SigType.java
index 48ad93e0ae3adbec071a80685b7356026df8a08e..05dd1906e7760c3e198853abf28175eac85c83c6 100644
--- a/core/java/src/net/i2p/crypto/SigType.java
+++ b/core/java/src/net/i2p/crypto/SigType.java
@@ -1,5 +1,6 @@
 package net.i2p.crypto;
 
+import java.security.GeneralSecurityException;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.security.Signature;
@@ -215,7 +216,9 @@ public enum SigType {
             }
             getDigestInstance();
             getHashInstance();
-        } catch (Exception e) {
+        } catch (GeneralSecurityException e) {
+            return false;
+        } catch (RuntimeException e) {
             return false;
         }
         return true;
diff --git a/core/java/src/net/i2p/crypto/TrustedUpdate.java b/core/java/src/net/i2p/crypto/TrustedUpdate.java
index b365a662af43211a7b6d54a9a6a3d890c4c56f34..5174292a2548355d253deb9e02e7c4d4d48b79ff 100644
--- a/core/java/src/net/i2p/crypto/TrustedUpdate.java
+++ b/core/java/src/net/i2p/crypto/TrustedUpdate.java
@@ -344,7 +344,11 @@ riCe6OlAEiNpcc6mMyIYYWFICbrDFTrDR3wXqwc/Jkcx6L5VVWoagpSzbo3yGhc=
             System.out.println("\r\nPrivate key written to: " + privateKeyFile);
             System.out.println("Public key written to: " + publicKeyFile);
             System.out.println("\r\nPublic key: " + signingPublicKey.toBase64() + "\r\n");
-        } catch (Exception e) {
+        } catch (IOException e) {
+            System.err.println("Error writing keys:");
+            e.printStackTrace();
+            return false;
+        } catch (DataFormatException e) {
             System.err.println("Error writing keys:");
             e.printStackTrace();
             return false;
@@ -758,7 +762,7 @@ riCe6OlAEiNpcc6mMyIYYWFICbrDFTrDR3wXqwc/Jkcx6L5VVWoagpSzbo3yGhc=
             bytesToSignInputStream = new SequenceInputStream(versionHeaderInputStream, fileInputStream);
             signature = _context.dsa().sign(bytesToSignInputStream, signingPrivateKey);
 
-        } catch (Exception e) {
+        } catch (IOException e) {
             if (_log.shouldLog(Log.ERROR))
                 _log.error("Error signing", e);
 
diff --git a/core/java/src/net/i2p/crypto/eddsa/math/GroupElement.java b/core/java/src/net/i2p/crypto/eddsa/math/GroupElement.java
index 268005ed771e159cde1fc15fc91b06f18eb01157..ec81b5d408449da9ef91de337f422fb1f234ef3f 100644
--- a/core/java/src/net/i2p/crypto/eddsa/math/GroupElement.java
+++ b/core/java/src/net/i2p/crypto/eddsa/math/GroupElement.java
@@ -722,7 +722,7 @@ public class GroupElement implements Serializable {
         if (!this.repr.equals(ge.repr)) {
             try {
                 ge = ge.toRep(this.repr);
-            } catch (Exception e) {
+            } catch (RuntimeException e) {
                 return false;
             }
         }
diff --git a/core/java/src/net/i2p/data/Certificate.java b/core/java/src/net/i2p/data/Certificate.java
index aa2624b8af8f1637698f42d165c3d2761da862c8..aefc34ce61f1ae5ba836bfedae2eb142f979f359 100644
--- a/core/java/src/net/i2p/data/Certificate.java
+++ b/core/java/src/net/i2p/data/Certificate.java
@@ -215,7 +215,7 @@ public class Certificate extends DataStructureImpl {
             throw new DataFormatException("Cert is too small [" + source.length + " off=" + offset + "]");
 
         int cur = offset;
-        _type = (int)DataHelper.fromLong(source, cur, 1);
+        _type = source[cur] & 0xff;
         cur++;
         int length = (int)DataHelper.fromLong(source, cur, 2);
         cur += 2;
diff --git a/core/java/src/net/i2p/data/DataHelper.java b/core/java/src/net/i2p/data/DataHelper.java
index 85bc2dee8f9aab61b2d0ccd218e627f1431582eb..ee78a95c645498696ff7109e0ba1e5a2c8e92dae 100644
--- a/core/java/src/net/i2p/data/DataHelper.java
+++ b/core/java/src/net/i2p/data/DataHelper.java
@@ -37,6 +37,9 @@ import java.util.Iterator;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Properties;
+import java.util.TimeZone;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.regex.Pattern;
 import java.util.zip.Deflater;
 
 import net.i2p.I2PAppContext;
@@ -1615,11 +1618,11 @@ public class DataHelper {
      * NOTE: formatDuration2() recommended in most cases for readability
      */
     public static String formatSize(long bytes) {
-        double val = bytes;
+        float val = bytes;
         int scale = 0;
-        while (val >= 1024) {
+        while (val >= 1024.0f) {
             scale++; 
-            val /= 1024;
+            val /= 1024.0f;
         }
         
         DecimalFormat fmt = new DecimalFormat("##0.00");
@@ -1888,4 +1891,54 @@ public class DataHelper {
         }
         return rv;
     }
+
+    /**
+     *  Same as s.split(regex) but caches the compiled pattern for speed.
+     *  This saves about 10 microseconds (Bulldozer) on subsequent invocations.
+     *
+     *  @param s non-null
+     *  @param regex non-null
+     *  @throws java.util.regex.PatternSyntaxException unchecked
+     *  @since 0.9.24
+     */
+    public static String[] split(String s, String regex) {
+        return split(s, regex, 0);
+    }
+
+    private static final ConcurrentHashMap<String, Pattern> patterns = new ConcurrentHashMap<String, Pattern>();
+
+    /**
+     *  Same as s.split(regex, limit) but caches the compiled pattern for speed.
+     *  This saves about 10 microseconds (Bulldozer) on subsequent invocations.
+     *
+     *  @param s non-null
+     *  @param regex non-null
+     *  @param limit result threshold
+     *  @throws java.util.regex.PatternSyntaxException unchecked
+     *  @since 0.9.24
+     */
+    public static String[] split(String s, String regex, int limit) {
+        Pattern p = patterns.get(regex);
+        if (p == null) {
+            p = Pattern.compile(regex);
+            patterns.putIfAbsent(regex, p);
+        }
+        return p.split(s, limit);
+    }
+
+    /**
+     *  The system's time zone, which is probably different from the
+     *  JVM time zone, because Router changes the JVM default to GMT.
+     *  It saves the old default in the context properties where we can get it.
+     *  Use this to format a time in local time zone with DateFormat.setTimeZone().
+     *
+     *  @return non-null
+     *  @since 0.9.24
+     */
+    public static TimeZone getSystemTimeZone(I2PAppContext ctx) {
+        String systemTimeZone = ctx.getProperty("i2p.systemTimeZone");
+        if (systemTimeZone != null)
+            return TimeZone.getTimeZone(systemTimeZone);
+        return TimeZone.getDefault();
+    }
 }
diff --git a/core/java/src/net/i2p/data/PrivateKeyFile.java b/core/java/src/net/i2p/data/PrivateKeyFile.java
index f42d8da15e7502bfbd428782337ef504540c4033..b77fd1350081c4e51a583750bfac3cc4a6573372 100644
--- a/core/java/src/net/i2p/data/PrivateKeyFile.java
+++ b/core/java/src/net/i2p/data/PrivateKeyFile.java
@@ -9,6 +9,7 @@ import java.io.InputStream;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.security.GeneralSecurityException;
+import java.security.NoSuchAlgorithmException; 
 import java.util.Locale;
 import java.util.Map;
 import java.util.Properties;
@@ -174,7 +175,10 @@ public class PrivateKeyFile {
                 pkf.write();
                 verifySignature(pkf.getDestination());
             }
-        } catch (Exception e) {
+        } catch (I2PException e) {
+            e.printStackTrace();
+            System.exit(1);
+        } catch (IOException e) {
             e.printStackTrace();
             System.exit(1);
         }
@@ -358,7 +362,7 @@ public class PrivateKeyFile {
         HashCash hc;
         try {
             hc = HashCash.mintCash(resource, effort);
-        } catch (Exception e) {
+        } catch (NoSuchAlgorithmException e) {
             return null;
         }
         System.out.println("Generation took: " + DataHelper.formatDuration(System.currentTimeMillis() - begin));
@@ -391,7 +395,9 @@ public class PrivateKeyFile {
         Destination d2;
         try {
             d2 = pkf2.getDestination();
-        } catch (Exception e) {
+        } catch (I2PException e) {
+            return null;
+        } catch (IOException e) {
             return null;
         }
         if (d2 == null)
@@ -500,7 +506,7 @@ public class PrivateKeyFile {
         long low = Long.MAX_VALUE;
         try {
             low = HashCash.estimateTime(hashEffort);
-        } catch (Exception e) {}
+        } catch (NoSuchAlgorithmException e) {}
         // takes a lot longer than the estimate usually...
         // maybe because the resource string is much longer than used in the estimate?
         return "It is estimated that generating a HashCash Certificate with value " + hashEffort +
diff --git a/core/java/src/net/i2p/data/i2cp/I2CPMessageReader.java b/core/java/src/net/i2p/data/i2cp/I2CPMessageReader.java
index 4dfbb8501c76193c31f5a0eb54aa95c55ac4a2db..4bfdbc158b79f54e34e3ca9939cae931995069aa 100644
--- a/core/java/src/net/i2p/data/i2cp/I2CPMessageReader.java
+++ b/core/java/src/net/i2p/data/i2cp/I2CPMessageReader.java
@@ -160,7 +160,7 @@ public class I2CPMessageReader {
         public void run() {
             try {
                 run2();
-            } catch (Exception e) {
+            } catch (RuntimeException e) {
                 _log.log(Log.CRIT, "Uncaught I2CP error", e);
                 _listener.readError(I2CPMessageReader.this, e);
                 cancelRunner();
@@ -193,7 +193,7 @@ public class I2CPMessageReader {
                     } catch (OutOfMemoryError oom) {
                         // ooms seen here... maybe log and keep going?
                         throw oom;
-                    } catch (Exception e) {
+                    } catch (RuntimeException e) {
                         _log.log(Log.CRIT, "Unhandled error reading I2CP stream", e);
                         _listener.disconnected(I2CPMessageReader.this);
                         cancelRunner();
diff --git a/core/java/src/net/i2p/stat/BufferedStatLog.java b/core/java/src/net/i2p/stat/BufferedStatLog.java
index 7c9b7b72318a6ac072eaaa7cb04cfcc732f2a9a7..5734e89488efcfe88ec0e2089a1d822af335a556 100644
--- a/core/java/src/net/i2p/stat/BufferedStatLog.java
+++ b/core/java/src/net/i2p/stat/BufferedStatLog.java
@@ -145,7 +145,7 @@ public class BufferedStatLog implements StatLog {
                             if (_log.shouldLog(Log.DEBUG))
                                 _log.debug("writing " + writeStart +"->"+ writeEnd);
                             writeEvents(writeStart, writeEnd);
-                        } catch (Exception e) {
+                        } catch (RuntimeException e) {
                             _log.error("error writing " + writeStart +"->"+ writeEnd, e);
                         }
                     }
diff --git a/core/java/src/net/i2p/util/EepGet.java b/core/java/src/net/i2p/util/EepGet.java
index 88cae89fea821082244584165adcbaefb8918249..19816bb41d2b58489bb7ac815a1ef0eaf4897cac 100644
--- a/core/java/src/net/i2p/util/EepGet.java
+++ b/core/java/src/net/i2p/util/EepGet.java
@@ -15,7 +15,8 @@ import java.net.InetSocketAddress;
 import java.net.MalformedURLException;
 import java.net.Socket;
 import java.net.UnknownHostException;
-import java.net.URL;
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.text.DecimalFormat;
 import java.util.ArrayList;
 import java.util.Date;
@@ -272,7 +273,7 @@ public class EepGet {
                     break;
               }  // switch
             } // while
-        } catch (Exception e) {
+        } catch (RuntimeException e) {
             e.printStackTrace();
             error = true;
         }
@@ -321,17 +322,17 @@ public class EepGet {
      * @return       a filename to save the resource as on local filesystem
      */
     public static String suggestName(String url) {
-        URL nameURL = null;  // URL object
+        URI nameURL = null;
         String name;         // suggested name
 
         try {
-            nameURL = new URL(url);
-        } catch (MalformedURLException e) {
+            nameURL = new URI(url);
+        } catch (URISyntaxException e) {
             System.err.println("Please enter a properly formed URL.");
             System.exit(1);
         }
 
-        String path = nameURL.getPath();  // discard any URI queries
+        String path = nameURL.getRawPath();  // discard any URI queries
 
         // if no file specified, eepget scrapes webpage - use domain as name
         Pattern slashes = Pattern.compile("/+");
@@ -722,24 +723,25 @@ public class EepGet {
         
         if (_redirectLocation != null) {
             // we also are here after a 407
-            //try {
+            try {
                 if (_redirectLocation.startsWith("http://")) {
                     _actualURL = _redirectLocation;
                 } else { 
                     // the Location: field has been required to be an absolute URI at least since
                     // RFC 1945 (HTTP/1.0 1996), so it isn't clear what the point of this is.
                     // This oddly adds a ":" even if no port, but that seems to work.
-                    URL url = new URL(_actualURL);
+                    URI url = new URI(_actualURL);
 		    if (_redirectLocation.startsWith("/"))
                         _actualURL = "http://" + url.getHost() + ":" + url.getPort() + _redirectLocation;
                     else
                         // this blows up completely on a redirect to https://, for example
                         _actualURL = "http://" + url.getHost() + ":" + url.getPort() + "/" + _redirectLocation;
                 }
-            // an MUE is an IOE
-            //} catch (MalformedURLException mue) {
-            //    throw new IOException("Redirected from an invalid URL");
-            //}
+            } catch (URISyntaxException use) {
+                IOException ioe = new MalformedURLException("Redirected to invalid URL");
+                ioe.initCause(use);
+                throw ioe;
+            }
 
             AuthState as = _authState;
             if (_responseCode == 407) {
@@ -1131,7 +1133,7 @@ public class EepGet {
     private int handleStatus(String line) {
         if (_log.shouldLog(Log.DEBUG))
             _log.debug("Status line: [" + line.trim() + "]");
-        String[] toks = line.split(" ", 3);
+        String[] toks = DataHelper.split(line, " ", 3);
         if (toks.length < 2) {
             if (_log.shouldLog(Log.WARN))
                 _log.warn("ERR: status "+  line);
@@ -1226,9 +1228,9 @@ public class EepGet {
         if (_shouldProxy) {
             _proxy = InternalSocket.getSocket(_proxyHost, _proxyPort);
         } else {
-            //try {
-                URL url = new URL(_actualURL);
-                if ("http".equals(url.getProtocol())) {
+            try {
+                URI url = new URI(_actualURL);
+                if ("http".equals(url.getScheme())) {
                     String host = url.getHost();
                     String hostlc = host.toLowerCase(Locale.US);
                     if (hostlc.endsWith(".i2p"))
@@ -1248,10 +1250,11 @@ public class EepGet {
                 } else {
                     throw new MalformedURLException("URL is not supported:" + _actualURL);
                 }
-            // an MUE is an IOE
-            //} catch (MalformedURLException mue) {
-            //    throw new IOException("Request URL is invalid");
-            //}
+            } catch (URISyntaxException use) {
+                IOException ioe = new MalformedURLException("Request URL is invalid");
+                ioe.initCause(use);
+                throw ioe;
+            }
         }
         _proxyIn = _proxy.getInputStream();
         if (!(_proxy instanceof InternalSocket))
@@ -1273,13 +1276,20 @@ public class EepGet {
         boolean post = false;
         if ( (_postData != null) && (_postData.length() > 0) )
             post = true;
-        URL url = new URL(_actualURL);
+        URI url;
+        try {
+            url = new URI(_actualURL);
+        } catch (URISyntaxException use) {
+            IOException ioe = new MalformedURLException("Bad URL");
+            ioe.initCause(use);
+            throw ioe;
+        }
         String host = url.getHost();
         if (host == null || host.length() <= 0)
             throw new MalformedURLException("Bad URL, no host");
         int port = url.getPort();
-        String path = url.getPath();
-        String query = url.getQuery();
+        String path = url.getRawPath();
+        String query = url.getRawQuery();
         if (_log.shouldLog(Log.DEBUG))
             _log.debug("Requesting " + _actualURL);
         // RFC 2616 sec 5.1.2 - full URL if proxied, absolute path only if not proxied
diff --git a/core/java/src/net/i2p/util/EepHead.java b/core/java/src/net/i2p/util/EepHead.java
index c1ddb62828001a4661c0e877ae10030b84a6328d..3c53314937885dd862a985b80e3c34f9c543fed5 100644
--- a/core/java/src/net/i2p/util/EepHead.java
+++ b/core/java/src/net/i2p/util/EepHead.java
@@ -6,7 +6,9 @@ import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
-import java.net.URL;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
 
 import gnu.getopt.Getopt;
 
@@ -107,7 +109,7 @@ public class EepHead extends EepGet {
                     break;
               }  // switch
             } // while
-        } catch (Exception e) {
+        } catch (RuntimeException e) {
             e.printStackTrace();
             error = true;
         }
@@ -176,24 +178,25 @@ public class EepHead extends EepGet {
         
         // Should we even follow redirects for HEAD?
         if (_redirectLocation != null) {
-            //try {
+            try {
                 if (_redirectLocation.startsWith("http://")) {
                     _actualURL = _redirectLocation;
                 } else { 
                     // the Location: field has been required to be an absolute URI at least since
                     // RFC 1945 (HTTP/1.0 1996), so it isn't clear what the point of this is.
                     // This oddly adds a ":" even if no port, but that seems to work.
-                    URL url = new URL(_actualURL);
+                    URI url = new URI(_actualURL);
 		    if (_redirectLocation.startsWith("/"))
                         _actualURL = "http://" + url.getHost() + ":" + url.getPort() + _redirectLocation;
                     else
                         // this blows up completely on a redirect to https://, for example
                         _actualURL = "http://" + url.getHost() + ":" + url.getPort() + "/" + _redirectLocation;
                 }
-            // an MUE is an IOE
-            //} catch (MalformedURLException mue) {
-            //    throw new IOException("Redirected from an invalid URL");
-            //}
+            } catch (URISyntaxException use) {
+                IOException ioe = new MalformedURLException("Redirected to invalid URL");
+                ioe.initCause(use);
+                throw ioe;
+            }
             AuthState as = _authState;
             if (_responseCode == 407) {
                 if (!_shouldProxy)
@@ -252,11 +255,18 @@ public class EepHead extends EepGet {
     @Override
     protected String getRequest() throws IOException {
         StringBuilder buf = new StringBuilder(512);
-        URL url = new URL(_actualURL);
+        URI url;
+        try {
+            url = new URI(_actualURL);
+        } catch (URISyntaxException use) {
+            IOException ioe = new MalformedURLException("Bad URL");
+            ioe.initCause(use);
+            throw ioe;
+        }
         String host = url.getHost();
         int port = url.getPort();
-        String path = url.getPath();
-        String query = url.getQuery();
+        String path = url.getRawPath();
+        String query = url.getRawQuery();
         if (_log.shouldLog(Log.DEBUG))
             _log.debug("Requesting " + _actualURL);
         // RFC 2616 sec 5.1.2 - full URL if proxied, absolute path only if not proxied
diff --git a/core/java/src/net/i2p/util/FortunaRandomSource.java b/core/java/src/net/i2p/util/FortunaRandomSource.java
index 9688e87648588f6bc01a199a3d339ebb59365450..e386e325c954ad5d311ca815c6471f48246843fe 100644
--- a/core/java/src/net/i2p/util/FortunaRandomSource.java
+++ b/core/java/src/net/i2p/util/FortunaRandomSource.java
@@ -11,6 +11,7 @@ package net.i2p.util;
 
 import gnu.crypto.prng.AsyncFortunaStandalone;
 
+import java.io.IOException;
 import java.security.SecureRandom;
 
 import net.i2p.I2PAppContext;
@@ -266,7 +267,7 @@ public class FortunaRandomSource extends RandomSource implements EntropyHarveste
             synchronized(_fortuna) {
                 _fortuna.addRandomBytes(data, offset, len);
             }
-        } catch (Exception e) {
+        } catch (RuntimeException e) {
             // AIOOBE seen, root cause unknown, ticket #1576
             Log log = _context.logManager().getLog(FortunaRandomSource.class);
             log.warn("feedEntropy()", e);
@@ -290,6 +291,6 @@ public class FortunaRandomSource extends RandomSource implements EntropyHarveste
                 rand.nextBytes(buf);
                 System.out.write(buf);
             }
-        } catch (Exception e) { e.printStackTrace(); }
+        } catch (IOException e) { e.printStackTrace(); }
     }
 }
diff --git a/core/java/src/net/i2p/util/I2PSSLSocketFactory.java b/core/java/src/net/i2p/util/I2PSSLSocketFactory.java
index 774415f4dfb71d52d5b2c48c6fc2d7c063025bcd..1b8342808a616cd66a4c50e8a8b6231385198a70 100644
--- a/core/java/src/net/i2p/util/I2PSSLSocketFactory.java
+++ b/core/java/src/net/i2p/util/I2PSSLSocketFactory.java
@@ -83,6 +83,7 @@ import javax.net.ssl.TrustManagerFactory;
 
 import net.i2p.I2PAppContext;
 import net.i2p.crypto.KeyStoreUtil;
+import net.i2p.data.DataHelper;
 
 import org.apache.http.conn.ssl.DefaultHostnameVerifier;
 import org.apache.http.conn.util.PublicSuffixList;
@@ -443,7 +444,7 @@ public class I2PSSLSocketFactory {
                 try {
                     if (line.charAt(0) == '#')
                         continue;
-                    String[] s = line.split(",");
+                    String[] s = DataHelper.split(line, ",");
                     String lc = s[0].toLowerCase(Locale.US);
                     tlds.add(lc);
                     i++;
diff --git a/core/java/src/net/i2p/util/LogManager.java b/core/java/src/net/i2p/util/LogManager.java
index ca2476de8379d44f4c64047e7936fb38cdf8ae08..94a39cf0ce34715dd6d8cd74a22f92133ed23226 100644
--- a/core/java/src/net/i2p/util/LogManager.java
+++ b/core/java/src/net/i2p/util/LogManager.java
@@ -22,7 +22,6 @@ import java.util.Map;
 import java.util.Properties;
 import java.util.Queue;
 import java.util.Set;
-import java.util.TimeZone;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -479,9 +478,7 @@ public class LogManager implements Flushable {
             if (!format.equals(""))
                 fmt.applyPattern(format);
             // the router sets the JVM time zone to UTC but saves the original here so we can get it
-            String systemTimeZone = _context.getProperty("i2p.systemTimeZone");
-            if (systemTimeZone != null)
-                fmt.setTimeZone(TimeZone.getTimeZone(systemTimeZone));
+            fmt.setTimeZone(DataHelper.getSystemTimeZone(_context));
             _dateFormatPattern = format;
             _dateFormat = fmt;
             return true;
diff --git a/core/java/src/net/i2p/util/LogWriterBase.java b/core/java/src/net/i2p/util/LogWriterBase.java
index b9b98e10d4e9723b4545d690fd29cafff0da9628..cc4fb93e6b57af45e5da1a6e0bfb37d43f6c180b 100644
--- a/core/java/src/net/i2p/util/LogWriterBase.java
+++ b/core/java/src/net/i2p/util/LogWriterBase.java
@@ -75,7 +75,7 @@ abstract class LogWriterBase implements Runnable {
                 if (_write && shouldReadConfig)
                     rereadConfig();
             }
-        } catch (Exception e) {
+        } catch (RuntimeException e) {
             System.err.println("Error writing the log: " + e);
             e.printStackTrace();
         }
diff --git a/core/java/src/net/i2p/util/NativeBigInteger.java b/core/java/src/net/i2p/util/NativeBigInteger.java
index afb380cf921bc234733a570f878990c05dcde365..96129b5ac37599fe24f49c95eabc6e4a774e68cb 100644
--- a/core/java/src/net/i2p/util/NativeBigInteger.java
+++ b/core/java/src/net/i2p/util/NativeBigInteger.java
@@ -35,6 +35,7 @@ import freenet.support.CPUInformation.UnknownCPUException;
 
 import net.i2p.I2PAppContext;
 import net.i2p.crypto.CryptoConstants;
+import net.i2p.data.DataHelper;
 
 /**
  * <p>BigInteger that takes advantage of the jbigi library for the modPow operation,
@@ -734,7 +735,7 @@ public class NativeBigInteger extends BigInteger {
             in = new BufferedReader(new InputStreamReader(new FileInputStream("/proc/cpuinfo"), "ISO-8859-1"), 4096);
             String line = null;
             while ( (line = in.readLine()) != null) {
-                String[] parts = line.split(":", 2);
+                String[] parts = DataHelper.split(line, ":", 2);
                 if (parts.length < 2)
                     continue;
                 String key = parts[0].trim().toLowerCase(Locale.US);
diff --git a/core/java/src/net/i2p/util/PartialEepGet.java b/core/java/src/net/i2p/util/PartialEepGet.java
index baab6e0397d2b557e6a76df453d7a9904e070a93..9d853137d5e2f608957370f28ec987d5a67af5fd 100644
--- a/core/java/src/net/i2p/util/PartialEepGet.java
+++ b/core/java/src/net/i2p/util/PartialEepGet.java
@@ -6,7 +6,8 @@ import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.net.MalformedURLException;
-import java.net.URL;
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.util.Locale;
 
 import gnu.getopt.Getopt;
@@ -106,7 +107,7 @@ public class PartialEepGet extends EepGet {
                     break;
               }  // switch
             } // while
-        } catch (Exception e) {
+        } catch (RuntimeException e) {
             e.printStackTrace();
             error = true;
         }
@@ -167,13 +168,20 @@ public class PartialEepGet extends EepGet {
     @Override
     protected String getRequest() throws IOException {
         StringBuilder buf = new StringBuilder(2048);
-        URL url = new URL(_actualURL);
+        URI url;
+        try {
+            url = new URI(_actualURL);
+        } catch (URISyntaxException use) {
+            IOException ioe = new MalformedURLException("Bad URL");
+            ioe.initCause(use);
+            throw ioe;
+        }
         String host = url.getHost();
         if (host == null || host.length() <= 0)
             throw new MalformedURLException("Bad URL, no host");
         int port = url.getPort();
-        String path = url.getPath();
-        String query = url.getQuery();
+        String path = url.getRawPath();
+        String query = url.getRawQuery();
         if (_log.shouldLog(Log.DEBUG))
             _log.debug("Requesting " + _actualURL);
         // RFC 2616 sec 5.1.2 - full URL if proxied, absolute path only if not proxied
diff --git a/core/java/src/net/i2p/util/ResettableGZIPInputStream.java b/core/java/src/net/i2p/util/ResettableGZIPInputStream.java
index 645bd29f398b278c5af354fc3343dd69cb894b8f..13eecb518eaa7166ae251166bfe7be64d72ba52f 100644
--- a/core/java/src/net/i2p/util/ResettableGZIPInputStream.java
+++ b/core/java/src/net/i2p/util/ResettableGZIPInputStream.java
@@ -124,7 +124,7 @@ public class ResettableGZIPInputStream extends InflaterInputStream {
     public long getTotalRead() {
         try {
             return inf.getBytesRead(); 
-        } catch (Exception e) {
+        } catch (RuntimeException e) {
             return 0;
         }
     }
@@ -136,7 +136,7 @@ public class ResettableGZIPInputStream extends InflaterInputStream {
     public long getTotalExpanded() { 
         try {
             return inf.getBytesWritten(); 
-        } catch (Exception e) {
+        } catch (RuntimeException e) {
             // possible NPE in some implementations
             return 0;
         }
@@ -149,7 +149,7 @@ public class ResettableGZIPInputStream extends InflaterInputStream {
     public long getRemaining() { 
         try {
             return inf.getRemaining(); 
-        } catch (Exception e) {
+        } catch (RuntimeException e) {
             // possible NPE in some implementations
             return 0;
         }
@@ -162,7 +162,7 @@ public class ResettableGZIPInputStream extends InflaterInputStream {
     public boolean getFinished() { 
         try {
             return inf.finished(); 
-        } catch (Exception e) {
+        } catch (RuntimeException e) {
             // possible NPE in some implementations
             return true;
         }
diff --git a/core/java/src/net/i2p/util/SSLEepGet.java b/core/java/src/net/i2p/util/SSLEepGet.java
index 3a6e7ebd9064714e5089eabf22bd74122f351a3c..c62da0c12518ca3d76396ea4a35d35fa1ef32925 100644
--- a/core/java/src/net/i2p/util/SSLEepGet.java
+++ b/core/java/src/net/i2p/util/SSLEepGet.java
@@ -46,7 +46,8 @@ import java.io.PipedInputStream;
 import java.io.PipedOutputStream;
 import java.net.InetSocketAddress;
 import java.net.MalformedURLException;
-import java.net.URL;
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.security.KeyStore;
 import java.security.GeneralSecurityException;
 import java.security.cert.CertificateException;
@@ -179,7 +180,7 @@ public class SSLEepGet extends EepGet {
                     break;
               }  // switch
             } // while
-        } catch (Exception e) {
+        } catch (RuntimeException e) {
             e.printStackTrace();
             error = true;
         }
@@ -369,7 +370,7 @@ public class SSLEepGet extends EepGet {
             System.out.println("      Valid To:   " + cert.getNotAfter());
             try {
                 cert.checkValidity();
-            } catch (Exception e) {
+            } catch (GeneralSecurityException e) {
                 System.out.println("      WARNING: Certificate is not currently valid, it cannot be used");
             }
             CertUtil.saveCert(cert, new File(name));
@@ -553,11 +554,11 @@ public class SSLEepGet extends EepGet {
 
         String req = getRequest();
 
-        //try {
-            URL url = new URL(_actualURL);
-            String host = null;
-            int port = 0;
-            if ("https".equals(url.getProtocol())) {
+        String host;
+        int port;
+        try {
+            URI url = new URI(_actualURL);
+            if ("https".equals(url.getScheme())) {
                 host = url.getHost();
                 if (host.toLowerCase(Locale.US).endsWith(".i2p"))
                     throw new MalformedURLException("I2P addresses unsupported");
@@ -589,10 +590,11 @@ public class SSLEepGet extends EepGet {
             } else {
                 throw new MalformedURLException("Only https supported: " + _actualURL);
             }
-        // an MUE is an IOE
-        //} catch (MalformedURLException mue) {
-        //    throw new IOException("Request URL is invalid");
-        //}
+        } catch (URISyntaxException use) {
+            IOException ioe = new MalformedURLException("Redirected to invalid URL");
+            ioe.initCause(use);
+            throw ioe;
+        }
 
         _proxyIn = _proxy.getInputStream();
         _proxyOut = _proxy.getOutputStream();
diff --git a/core/java/src/net/i2p/util/ShellCommand.java b/core/java/src/net/i2p/util/ShellCommand.java
index 955830fe1e0a71844d5d9ad29c1e8a36bf3312ab..4a5daac552c9660423be42cf2ab6bd603fb62cf4 100644
--- a/core/java/src/net/i2p/util/ShellCommand.java
+++ b/core/java/src/net/i2p/util/ShellCommand.java
@@ -439,7 +439,7 @@ public class ShellCommand {
                     System.out.println("ShellCommand waiting for \"" + name + '\"');
                 try {
                     process.waitFor();
-                } catch (Exception e) {
+                } catch (InterruptedException e) {
                     if (DEBUG) {
                         System.out.println("ShellCommand exception waiting for \"" + name + '\"');
                         e.printStackTrace();
@@ -457,7 +457,7 @@ public class ShellCommand {
                 if (process.exitValue() > 0)
                     return false;
             }
-        } catch (Exception e) {
+        } catch (IOException e) {
             // probably IOException, file not found from exec()
             if (DEBUG) {
                 System.out.println("ShellCommand execute exception for \"" + name + '\"');
diff --git a/core/java/src/net/metanotion/io/block/BlockFile.java b/core/java/src/net/metanotion/io/block/BlockFile.java
index 01870220489432d4d73ba242392ca9193661e9bd..122f30796e22c9bcbe1252b8482cd35762a7daa5 100644
--- a/core/java/src/net/metanotion/io/block/BlockFile.java
+++ b/core/java/src/net/metanotion/io/block/BlockFile.java
@@ -147,7 +147,7 @@ public class BlockFile implements Closeable {
 			bf.bfck(true);
 			bf.close();
 			raif.close();
-		} catch (Exception e) {
+		} catch (IOException e) {
 			e.printStackTrace();
 		}
 	}
diff --git a/installer/java/src/net/i2p/installer/Exec.java b/installer/java/src/net/i2p/installer/Exec.java
index b8488ceff2704c25ff5c64a62da42262c8671c54..de7419202b7784239a3caf6244b7005b44a18dc2 100644
--- a/installer/java/src/net/i2p/installer/Exec.java
+++ b/installer/java/src/net/i2p/installer/Exec.java
@@ -1,6 +1,7 @@
 package net.i2p.installer;
 
 import java.io.File;
+import java.io.IOException;
 
 /**
  * <p>This class can be used by the installer to execute shell commands.</p>
@@ -20,8 +21,9 @@ public class Exec {
             // http://cephas.net/blog/2004/03/23/external_applications_javas_runtimeexec.html
             try { proc.exitValue(); } catch (Throwable t) { }
             Runtime.getRuntime().halt(0);
-
-        } catch (Exception e) {
+        } catch (IOException e) {
+            e.printStackTrace();
+        } catch (RuntimeException e) {
             e.printStackTrace();
         }
     }
diff --git a/installer/tools/java/src/net/i2p/router/networkdb/kademlia/BundleRouterInfos.java b/installer/tools/java/src/net/i2p/router/networkdb/kademlia/BundleRouterInfos.java
index f916e976f1ce30b261652692a1cb68bfe5a4b6bc..cab5991d3356e38bfee18f32f4cb1f51677c49a6 100644
--- a/installer/tools/java/src/net/i2p/router/networkdb/kademlia/BundleRouterInfos.java
+++ b/installer/tools/java/src/net/i2p/router/networkdb/kademlia/BundleRouterInfos.java
@@ -24,6 +24,7 @@ import java.util.Properties;
 import gnu.getopt.Getopt;
 
 import net.i2p.I2PAppContext;
+import net.i2p.data.DataFormatException;
 import net.i2p.data.Hash;
 import net.i2p.data.router.RouterAddress;
 import net.i2p.data.router.RouterInfo;
@@ -109,7 +110,9 @@ public class BundleRouterInfos {
             RouterInfo ri = new RouterInfo();
             ri.readBytes(fis, true);  // true = verify sig on read
             me = ri.getIdentity().getHash();
-        } catch (Exception e) {
+        } catch (IOException e) {
+            //System.out.println("Can't determine our identity");
+        } catch (DataFormatException e) {
             //System.out.println("Can't determine our identity");
         } finally {
             if (fis != null) try { fis.close(); } catch (IOException ioe) {}
@@ -209,7 +212,9 @@ public class BundleRouterInfos {
                     copied++;
                 else
                     System.out.println("Failed copy of " + file + " to " + toDir);
-            } catch (Exception e) {
+            } catch (IOException e) {
+                System.out.println("Skipping bad " + file);
+            } catch (DataFormatException e) {
                 System.out.println("Skipping bad " + file);
             } finally {
                 if (fis != null) try { fis.close(); } catch (IOException ioe) {}
diff --git a/router/java/src/net/i2p/data/i2np/DatabaseSearchReplyMessage.java b/router/java/src/net/i2p/data/i2np/DatabaseSearchReplyMessage.java
index 4a5c946385772afd169e6ff7f19f3e8d161034c4..1df280349ef78e11a141b438257f815ef1e02ab1 100644
--- a/router/java/src/net/i2p/data/i2np/DatabaseSearchReplyMessage.java
+++ b/router/java/src/net/i2p/data/i2np/DatabaseSearchReplyMessage.java
@@ -68,7 +68,7 @@ public class DatabaseSearchReplyMessage extends FastI2NPMessageImpl {
         curIndex += Hash.HASH_LENGTH;
         //_key = new Hash(keyData);
         
-        int num = (int)DataHelper.fromLong(data, curIndex, 1);
+        int num = data[curIndex] & 0xff;
         curIndex++;
         
         _peerHashes.clear();
diff --git a/router/java/src/net/i2p/data/i2np/DeliveryInstructions.java b/router/java/src/net/i2p/data/i2np/DeliveryInstructions.java
index 9669d6f91fa5f176dffaf2049bfb9a56ac1345a6..5f59415124134a6511efdfd22dee7ebd35324a12 100644
--- a/router/java/src/net/i2p/data/i2np/DeliveryInstructions.java
+++ b/router/java/src/net/i2p/data/i2np/DeliveryInstructions.java
@@ -209,7 +209,7 @@ public class DeliveryInstructions extends DataStructureImpl {
     
     public int readBytes(byte data[], int offset) throws DataFormatException {
         int cur = offset;
-        long flags = DataHelper.fromLong(data, cur, 1);
+        int flags = data[cur] & 0xff;
         cur++;
         //if (_log.shouldLog(Log.DEBUG))
         //    _log.debug("Read flags: " + flags + " mode: " +  flagMode(flags));
diff --git a/router/java/src/net/i2p/data/i2np/FastI2NPMessageImpl.java b/router/java/src/net/i2p/data/i2np/FastI2NPMessageImpl.java
index 564d0f7262bf2ecb2a1c253079bb0f0e2833eda1..912a9e2b9eff5dda42d0a2c3bb93c154b559e163 100644
--- a/router/java/src/net/i2p/data/i2np/FastI2NPMessageImpl.java
+++ b/router/java/src/net/i2p/data/i2np/FastI2NPMessageImpl.java
@@ -85,7 +85,7 @@ public abstract class FastI2NPMessageImpl extends I2NPMessageImpl {
             throw new I2NPMessageException("Payload is too short " + maxLen);
         int cur = offset;
         if (type < 0) {
-            type = (int)DataHelper.fromLong(data, cur, 1);
+            type = data[cur] & 0xff;
             cur++;
         }
         _uniqueId = DataHelper.fromLong(data, cur, 4);
diff --git a/router/java/src/net/i2p/data/i2np/GarlicClove.java b/router/java/src/net/i2p/data/i2np/GarlicClove.java
index 628874d8175649cf8daa1d7d265777a6fce113f8..d19435321e0e1f42c005af894894ff0c4c44447c 100644
--- a/router/java/src/net/i2p/data/i2np/GarlicClove.java
+++ b/router/java/src/net/i2p/data/i2np/GarlicClove.java
@@ -158,7 +158,7 @@ public class GarlicClove extends DataStructureImpl {
             if (m.length <= 0)
                 throw new RuntimeException("foo, returned 0 length");
             out.write(m);
-        } catch (Exception e) {
+        } catch (RuntimeException e) {
             throw new DataFormatException("Unable to write the clove: " + _msg + " to " + out, e);
         }
         DataHelper.writeLong(out, 4, _cloveId);
@@ -187,7 +187,7 @@ public class GarlicClove extends DataStructureImpl {
             byte m[] = _msg.toByteArray();
             System.arraycopy(m, 0, rv, offset, m.length);
             offset += m.length;
-        } catch (Exception e) { throw new RuntimeException("Unable to write: " + _msg + ": " + e.getMessage()); }
+        } catch (RuntimeException e) { throw new RuntimeException("Unable to write: " + _msg + ": " + e.getMessage()); }
         DataHelper.toLong(rv, offset, 4, _cloveId);
         offset += 4;
         DataHelper.toDate(rv, offset, _expiration.getTime());
diff --git a/router/java/src/net/i2p/data/i2np/I2NPMessageHandler.java b/router/java/src/net/i2p/data/i2np/I2NPMessageHandler.java
index fe1225f74fca8cda0b3038fbfdd533414bd851a7..224918ef48eca9a7a0f45df9f6e4d1cb6ebcc40d 100644
--- a/router/java/src/net/i2p/data/i2np/I2NPMessageHandler.java
+++ b/router/java/src/net/i2p/data/i2np/I2NPMessageHandler.java
@@ -59,7 +59,7 @@ public class I2NPMessageHandler {
                 _lastSize = msg.readBytes(in, type, _messageBuffer);
             } catch (I2NPMessageException ime) {
                 throw ime;
-            } catch (Exception e) {
+            } catch (RuntimeException e) {
                 if (_log.shouldLog(Log.WARN))
                     _log.warn("Error reading the stream", e);
                 throw new I2NPMessageException("Unknown error reading the " + msg.getClass().getSimpleName(), e); 
@@ -109,7 +109,7 @@ public class I2NPMessageHandler {
     public int readMessage(byte data[], int offset, int maxLen) throws I2NPMessageException {
         int cur = offset;
         // we will assume that maxLen is >= 1 here. It's checked to be >= 16 in readBytes()
-        int type = (int)DataHelper.fromLong(data, cur, 1);
+        int type = data[cur] & 0xff;
         cur++;
         _lastReadBegin = System.currentTimeMillis();
         I2NPMessage msg = I2NPMessageImpl.createMessage(_context, type);
@@ -131,7 +131,7 @@ public class I2NPMessageHandler {
             cur += _lastSize;
         } catch (I2NPMessageException ime) {
             throw ime;
-        } catch (Exception e) {
+        } catch (RuntimeException e) {
             if (_log.shouldLog(Log.WARN))
                 _log.warn("Error reading the stream", e);
             throw new I2NPMessageException("Unknown error reading the " + msg.getClass().getSimpleName(), e); 
diff --git a/router/java/src/net/i2p/data/i2np/I2NPMessageImpl.java b/router/java/src/net/i2p/data/i2np/I2NPMessageImpl.java
index 5f14c3fef71738f0c3a931c49739d5c69fe664a6..062e310b12dbef001379b990b49289a7a8169025 100644
--- a/router/java/src/net/i2p/data/i2np/I2NPMessageImpl.java
+++ b/router/java/src/net/i2p/data/i2np/I2NPMessageImpl.java
@@ -197,7 +197,7 @@ public abstract class I2NPMessageImpl extends DataStructureImpl implements I2NPM
             throw new I2NPMessageException("Payload is too short " + maxLen);
         int cur = offset;
         if (type < 0) {
-            type = (int)DataHelper.fromLong(data, cur, 1);
+            type = data[cur] & 0xff;
             cur++;
         }
         _uniqueId = DataHelper.fromLong(data, cur, 4);
@@ -413,7 +413,7 @@ public abstract class I2NPMessageImpl extends DataStructureImpl implements I2NPM
      */
     public static I2NPMessage fromRawByteArray(I2PAppContext ctx, byte buffer[], int offset,
                                                int len, I2NPMessageHandler handler) throws I2NPMessageException {
-        int type = (int)DataHelper.fromLong(buffer, offset, 1);
+        int type = buffer[offset] & 0xff;
         offset++;
         I2NPMessage msg = createMessage(ctx, type);
         if (msg == null)
diff --git a/router/java/src/net/i2p/data/i2np/I2NPMessageReader.java b/router/java/src/net/i2p/data/i2np/I2NPMessageReader.java
index f253cfed22a46660292a60fa43cdd47685bd011b..d23a489d60964cbf7f3b37ddd0e139fecec9df51 100644
--- a/router/java/src/net/i2p/data/i2np/I2NPMessageReader.java
+++ b/router/java/src/net/i2p/data/i2np/I2NPMessageReader.java
@@ -163,7 +163,7 @@ public class I2NPMessageReader {
                             _log.warn("IO Error handling message", ioe);
                         _listener.disconnected(I2NPMessageReader.this);
                         cancelRunner();
-                    } catch (Exception e) {
+                    } catch (RuntimeException e) {
                         _log.log(Log.CRIT, "error reading msg!", e);
                         _listener.readError(I2NPMessageReader.this, e);
                         _listener.disconnected(I2NPMessageReader.this);
diff --git a/router/java/src/net/i2p/data/i2np/VariableTunnelBuildMessage.java b/router/java/src/net/i2p/data/i2np/VariableTunnelBuildMessage.java
index b33cba76147a962916f89804c27f3e77961ab85b..52aa565de7262ad973a84e44b24def0c247aac34 100644
--- a/router/java/src/net/i2p/data/i2np/VariableTunnelBuildMessage.java
+++ b/router/java/src/net/i2p/data/i2np/VariableTunnelBuildMessage.java
@@ -29,7 +29,7 @@ public class VariableTunnelBuildMessage extends TunnelBuildMessage {
     @Override
     public void readMessage(byte[] data, int offset, int dataSize, int type) throws I2NPMessageException {
         // message type will be checked in super()
-        int r = (int)DataHelper.fromLong(data, offset, 1);
+        int r = data[offset] & 0xff;
         if (r <= 0 || r > MAX_RECORD_COUNT)
             throw new I2NPMessageException("Bad record count " + r);
         RECORD_COUNT = r;
diff --git a/router/java/src/net/i2p/data/i2np/VariableTunnelBuildReplyMessage.java b/router/java/src/net/i2p/data/i2np/VariableTunnelBuildReplyMessage.java
index 368104a2adc9df4a34f8730ab24766964ee89c3c..575f5563002a1a424713537d96a27549e5382a62 100644
--- a/router/java/src/net/i2p/data/i2np/VariableTunnelBuildReplyMessage.java
+++ b/router/java/src/net/i2p/data/i2np/VariableTunnelBuildReplyMessage.java
@@ -31,7 +31,7 @@ public class VariableTunnelBuildReplyMessage extends TunnelBuildReplyMessage {
     @Override
     public void readMessage(byte[] data, int offset, int dataSize, int type) throws I2NPMessageException {
         // message type will be checked in super()
-        int r = (int)DataHelper.fromLong(data, offset, 1);
+        int r = data[offset] & 0xff;
         if (r <= 0 || r > MAX_RECORD_COUNT)
             throw new I2NPMessageException("Bad record count " + r);
         RECORD_COUNT = r;
diff --git a/router/java/src/net/i2p/data/router/RouterInfo.java b/router/java/src/net/i2p/data/router/RouterInfo.java
index 0d60c6dc03eb47771d62f02198f79ef6701d1b37..f2f9cdb7f51b346cccfc8d25dda1e2dd0f203177 100644
--- a/router/java/src/net/i2p/data/router/RouterInfo.java
+++ b/router/java/src/net/i2p/data/router/RouterInfo.java
@@ -724,7 +724,10 @@ public class RouterInfo extends DatabaseEntry {
                      System.err.println("Router info " + args[i] + " is invalid");
                      fail = true;
                   }
-             } catch (Exception e) {
+             } catch (IOException e) {
+                 System.err.println("Error reading " + args[i] + ": " + e);
+                 fail = true;
+             } catch (DataFormatException e) {
                  System.err.println("Error reading " + args[i] + ": " + e);
                  fail = true;
              } finally {
diff --git a/router/java/src/net/i2p/router/InNetMessagePool.java b/router/java/src/net/i2p/router/InNetMessagePool.java
index fb4c2a632c2f85f87fbabee70c8d0db853794a09..bfd3d696ea73c666416e2f52a9111e05aaa601fa 100644
--- a/router/java/src/net/i2p/router/InNetMessagePool.java
+++ b/router/java/src/net/i2p/router/InNetMessagePool.java
@@ -434,7 +434,7 @@ public class InNetMessagePool implements Service {
                     
                 } catch (OutOfMemoryError oome) {
                     throw oome;
-                } catch (Exception e) {
+                } catch (RuntimeException e) {
                     if (_log.shouldLog(Log.CRIT))
                         _log.log(Log.CRIT, "Error in the tunnel gateway dispatcher", e);
                 }
@@ -467,7 +467,7 @@ public class InNetMessagePool implements Service {
                     
                 } catch (OutOfMemoryError oome) {
                     throw oome;
-                } catch (Exception e) {
+                } catch (RuntimeException e) {
                     if (_log.shouldLog(Log.CRIT))
                         _log.log(Log.CRIT, "Error in the tunnel data dispatcher", e);
                 }
diff --git a/router/java/src/net/i2p/router/JobTiming.java b/router/java/src/net/i2p/router/JobTiming.java
index f934f1556d4c595893f68e40432bded72245a6ce..c9c0efb03abe03e916bf82dae16c3aef7e39ff0d 100644
--- a/router/java/src/net/i2p/router/JobTiming.java
+++ b/router/java/src/net/i2p/router/JobTiming.java
@@ -16,7 +16,7 @@ import net.i2p.util.Clock;
  *
  * For use by the router only. Not to be used by applications or plugins.
  */
-public class JobTiming implements Clock.ClockUpdateListener, RouterClock.ClockShiftListener {
+public class JobTiming implements Clock.ClockUpdateListener {
     private volatile long _start;
     private volatile long _actualStart;
     private volatile long _actualEnd;
@@ -82,8 +82,4 @@ public class JobTiming implements Clock.ClockUpdateListener, RouterClock.ClockSh
         if (_actualEnd != 0)
             _actualEnd += delta;
     }
-
-    public void clockShift(long delta) {
-        offsetChanged(delta);
-    }
 }
diff --git a/router/java/src/net/i2p/router/Router.java b/router/java/src/net/i2p/router/Router.java
index 426da4a757cc9796c47ab2a1d439bfa2292e7f28..99073c366a703df562e073d025ef3c351b3c58b8 100644
--- a/router/java/src/net/i2p/router/Router.java
+++ b/router/java/src/net/i2p/router/Router.java
@@ -9,6 +9,7 @@ package net.i2p.router;
  */
 
 import java.io.File;
+import java.io.IOException;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
@@ -648,7 +649,7 @@ public class Router implements RouterClock.ClockShiftListener {
                 //else
                 //    System.err.println("WARNING: Configuration file " + filename + " does not exist");
             }
-        } catch (Exception ioe) {
+        } catch (IOException ioe) {
             if (log != null)
                 log.error("Error loading the router configuration from " + filename, ioe);
             else
@@ -1351,7 +1352,7 @@ public class Router implements RouterClock.ClockShiftListener {
                 ordered.putAll(_config);
                 DataHelper.storeProps(ordered, new File(_configFilename));
             }
-        } catch (Exception ioe) {
+        } catch (IOException ioe) {
                 // warning, _log will be null when called from constructor
                 if (_log != null)
                     _log.error("Error saving the config to " + _configFilename, ioe);
diff --git a/router/java/src/net/i2p/router/dummy/VMCommSystem.java b/router/java/src/net/i2p/router/dummy/VMCommSystem.java
index 13b8aff0a244c6b748e8a240548e541c3d80eb90..29f232370e72983399e1655026531252f183b464 100644
--- a/router/java/src/net/i2p/router/dummy/VMCommSystem.java
+++ b/router/java/src/net/i2p/router/dummy/VMCommSystem.java
@@ -8,6 +8,7 @@ import java.util.Map;
 
 import net.i2p.data.Hash;
 import net.i2p.data.i2np.I2NPMessage;
+import net.i2p.data.i2np.I2NPMessageException;
 import net.i2p.data.i2np.I2NPMessageHandler;
 import net.i2p.router.CommSystemFacade;
 import net.i2p.router.JobImpl;
@@ -121,7 +122,7 @@ public class VMCommSystem extends CommSystemFacade {
                     ReceiveJob.this.getContext().statManager().addRateData("transport.receiveMessageLarge", 1, 1);
 
                 _ctx.inNetMessagePool().add(msg, null, _from);
-            } catch (Exception e) {
+            } catch (I2NPMessageException e) {
                 _log.error("Error reading/formatting a VM message? Something is not right...", e);
             }
         }
diff --git a/router/java/src/net/i2p/router/message/GarlicMessageParser.java b/router/java/src/net/i2p/router/message/GarlicMessageParser.java
index df83702a28780516928043e53b87530db4aea483..ef0e5f0b7afe7f41ea3556eddafa2d1d6da46f64 100644
--- a/router/java/src/net/i2p/router/message/GarlicMessageParser.java
+++ b/router/java/src/net/i2p/router/message/GarlicMessageParser.java
@@ -75,7 +75,7 @@ public class GarlicMessageParser {
     private CloveSet readCloveSet(byte data[]) throws DataFormatException {
         int offset = 0;
         
-        int numCloves = (int)DataHelper.fromLong(data, offset, 1);
+        int numCloves = data[offset] & 0xff;
         offset++;
         if (_log.shouldLog(Log.DEBUG))
             _log.debug("# cloves to read: " + numCloves);
diff --git a/router/java/src/net/i2p/router/message/OutboundClientMessageOneShotJob.java b/router/java/src/net/i2p/router/message/OutboundClientMessageOneShotJob.java
index 8a517a806f1a7296e50f2f0f94105e5ea0ec971b..fd45bc7520fe01c2df19026c03262bad5f76ee5f 100644
--- a/router/java/src/net/i2p/router/message/OutboundClientMessageOneShotJob.java
+++ b/router/java/src/net/i2p/router/message/OutboundClientMessageOneShotJob.java
@@ -949,15 +949,19 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
                 if (_outTunnel.getLength() > 0)
                     size = ((size + 1023) / 1024) * 1024; // messages are in ~1KB blocks
                 
-                for (int i = 0; i < _outTunnel.getLength(); i++) {
+                // skip ourselves at first hop
+                for (int i = 1; i < _outTunnel.getLength(); i++) {
                     getContext().profileManager().tunnelTestSucceeded(_outTunnel.getPeer(i), sendTime);
                     getContext().profileManager().tunnelDataPushed(_outTunnel.getPeer(i), sendTime, size);
                 }
                 _outTunnel.incrementVerifiedBytesTransferred(size);
             }
-            if (_inTunnel != null)
-                for (int i = 0; i < _inTunnel.getLength(); i++)
+            if (_inTunnel != null) {
+                // skip ourselves at last hop
+                for (int i = 0; i < _inTunnel.getLength() - 1; i++) {
                     getContext().profileManager().tunnelTestSucceeded(_inTunnel.getPeer(i), sendTime);
+                }
+            }
         }
 
         public void setMessage(I2NPMessage msg) {}
diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java b/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java
index a04a339ccb1f097b4ba51cc7bdd1a778bca9b299..f915b03f730baeb7d206de5033ec201aaf470a50 100644
--- a/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java
+++ b/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java
@@ -543,7 +543,7 @@ public class PersistentDataStore extends TransientDataStore {
                     if (_log.shouldLog(Log.INFO))
                         _log.info("Unable to read the router reference in " + _routerFile.getName(), ioe);
                     corrupt = true;
-                } catch (Exception e) {
+                } catch (RuntimeException e) {
                     // key certificate problems, etc., don't let one bad RI kill the whole thing
                     if (_log.shouldLog(Log.INFO))
                         _log.info("Unable to read the router reference in " + _routerFile.getName(), e);
@@ -666,7 +666,7 @@ public class PersistentDataStore extends TransientDataStore {
                 return null;
             Hash h = Hash.create(b);
             return h;
-        } catch (Exception e) {
+        } catch (RuntimeException e) {
             // static
             //_log.warn("Unable to fetch the key from [" + filename + "]", e);
             return null;
diff --git a/router/java/src/net/i2p/router/networkdb/reseed/ReseedChecker.java b/router/java/src/net/i2p/router/networkdb/reseed/ReseedChecker.java
index d6fc00f4e6a9fe343edeca80ab29dd119f516fef..25cd334ac8670b02dd720f820e8e239f0223d43c 100644
--- a/router/java/src/net/i2p/router/networkdb/reseed/ReseedChecker.java
+++ b/router/java/src/net/i2p/router/networkdb/reseed/ReseedChecker.java
@@ -3,7 +3,7 @@ package net.i2p.router.networkdb.reseed;
 import java.io.File;
 import java.io.InputStream;
 import java.io.IOException;
-import java.net.URL;
+import java.net.URI;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import net.i2p.data.DataHelper;
@@ -131,7 +131,7 @@ public class ReseedChecker {
      *  @throws IllegalArgumentException if it doesn't end with zip or su3
      *  @since 0.9.19
      */
-    public boolean requestReseed(URL url) throws IllegalArgumentException {
+    public boolean requestReseed(URI url) throws IllegalArgumentException {
         if (_inProgress.compareAndSet(false, true)) {
             Reseeder reseeder = new Reseeder(_context, this);
             try {
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 fa8d4c2d21665132a3da5a9082bff9905c04ec59..e908db35f188aae9ae4a3ae82793812541ae3add 100644
--- a/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java
+++ b/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java
@@ -7,10 +7,8 @@ import java.io.FileOutputStream;
 import java.io.InputStream;
 import java.io.IOException;
 import java.io.OutputStream;
-import java.net.MalformedURLException;
 import java.net.URI;
 import java.net.URISyntaxException;
-import java.net.URL;
 import java.util.Arrays;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -152,7 +150,7 @@ public class Reseeder {
      *  @throws IllegalArgumentException if it doesn't end with zip or su3
      *  @since 0.9.19
      */
-    void requestReseed(URL url) throws IllegalArgumentException {
+    void requestReseed(URI url) throws IllegalArgumentException {
         ReseedRunner reseedRunner = new ReseedRunner(url);
         // set to daemon so it doesn't hang a shutdown
         Thread reseed = new I2PAppThread(reseedRunner, "Reseed", true);
@@ -238,7 +236,7 @@ public class Reseeder {
         /** bytes per sec for each su3 downloaded */
         private final List<Long> _bandwidths;
         private static final int MAX_DATE_SETS = 2;
-        private final URL _url;
+        private final URI _url;
 
         /**
          *  Start a reseed from the default URL list
@@ -255,7 +253,7 @@ public class Reseeder {
          *  @throws IllegalArgumentException if it doesn't end with zip or su3
          *  @since 0.9.19
          */
-        public ReseedRunner(URL url) throws IllegalArgumentException {
+        public ReseedRunner(URI url) throws IllegalArgumentException {
             String lc = url.getPath().toLowerCase(Locale.US);
             if (!(lc.endsWith(".zip") || lc.endsWith(".su3")))
                 throw new IllegalArgumentException("Reseed URL must end with .zip or .su3");
@@ -411,7 +409,7 @@ public class Reseeder {
         * @return count of routerinfos successfully fetched, or -1 if no valid URLs
         */
         private int reseed(boolean echoStatus) {
-            List<URL> URLList = new ArrayList<URL>();
+            List<URI> URLList = new ArrayList<URI>();
             String URLs = _context.getProperty(PROP_RESEED_URL);
             boolean defaulted = URLs == null;
             boolean SSLDisable = _context.getBooleanProperty(PROP_SSL_DISABLE);
@@ -428,29 +426,29 @@ public class Reseeder {
                     if (!u.endsWith("/"))
                         u = u + '/';
                     try {
-                        URLList.add(new URL(u));
-                    } catch (MalformedURLException mue) {}
+                        URLList.add(new URI(u));
+                    } catch (URISyntaxException mue) {}
                 }
                 Collections.shuffle(URLList, _context.random());
                 if (!SSLDisable && !SSLRequired) {
                     // put the non-SSL at the end of the SSL
-                    List<URL> URLList2 = new ArrayList<URL>();
+                    List<URI> URLList2 = new ArrayList<URI>();
                     tok = new StringTokenizer(DEFAULT_SEED_URL, " ,");
                     while (tok.hasMoreTokens()) {
                         String u = tok.nextToken().trim();
                         if (!u.endsWith("/"))
                             u = u + '/';
                         try {
-                            URLList2.add(new URL(u));
-                        } catch (MalformedURLException mue) {}
+                            URLList2.add(new URI(u));
+                        } catch (URISyntaxException mue) {}
                     }
                     Collections.shuffle(URLList2, _context.random());
                     URLList.addAll(URLList2);
                 }
             } else {
                 // custom list given
-                List<URL> SSLList = new ArrayList<URL>();
-                List<URL> nonSSLList = new ArrayList<URL>();
+                List<URI> SSLList = new ArrayList<URI>();
+                List<URI> nonSSLList = new ArrayList<URI>();
                 StringTokenizer tok = new StringTokenizer(URLs, " ,");
                 while (tok.hasMoreTokens()) {
                     // format tokens
@@ -460,12 +458,12 @@ public class Reseeder {
                     // check if ssl or not then add to respective list
                     if (u.startsWith("https")) {
                         try {
-                            SSLList.add(new URL(u));
-                        } catch (MalformedURLException mue) {}
+                            SSLList.add(new URI(u));
+                        } catch (URISyntaxException mue) {}
                     } else {
                         try {
-                            nonSSLList.add(new URL(u));
-                        } catch (MalformedURLException mue) {}
+                            nonSSLList.add(new URI(u));
+                        } catch (URISyntaxException mue) {}
                     }
                 }
                 // shuffle lists
@@ -481,8 +479,8 @@ public class Reseeder {
             }
             if (!isSNISupported()) {
                 try {
-                    URLList.remove(new URL("https://netdb.i2p2.no/"));
-                } catch (MalformedURLException mue) {}
+                    URLList.remove(new URI("https://netdb.i2p2.no/"));
+                } catch (URISyntaxException mue) {}
             }
             if (URLList.isEmpty()) {
                 System.out.println("No valid reseed URLs");
@@ -500,19 +498,19 @@ public class Reseeder {
         * @param echoStatus apparently always false
         * @return count of routerinfos successfully fetched
         */
-        private int reseed(List<URL> URLList, boolean echoStatus) {
+        private int reseed(List<URI> URLList, boolean echoStatus) {
             int total = 0;
             for (int i = 0; i < URLList.size() && _isRunning; i++) {
                 if (_context.router().gracefulShutdownInProgress()) {
                     System.out.println("Reseed aborted, shutdown in progress");
                     return total;
                 }
-                URL url = URLList.get(i);
+                URI url = URLList.get(i);
                 int dl = 0;
                 if (ENABLE_SU3) {
                     try {
-                        dl = reseedSU3(new URL(url.toString() + SU3_FILENAME), echoStatus);
-                    } catch (MalformedURLException mue) {}
+                        dl = reseedSU3(new URI(url.toString() + SU3_FILENAME), echoStatus);
+                    } catch (URISyntaxException mue) {}
                 }
                 if (ENABLE_NON_SU3) {
                     if (dl <= 0)
@@ -556,7 +554,7 @@ public class Reseeder {
          * @param echoStatus apparently always false
          * @return count of routerinfos successfully fetched
          **/
-        private int reseedOne(URL seedURL, boolean echoStatus) {
+        private int reseedOne(URI seedURL, boolean echoStatus) {
             try {
                 // Don't use context clock as we may be adjusting the time
                 final long timeLimit = System.currentTimeMillis() + MAX_TIME_PER_HOST;
@@ -627,7 +625,7 @@ public class Reseeder {
                             if (fetched % 60 == 0)
                                 System.out.println();
                         }
-                    } catch (Exception e) {
+                    } catch (RuntimeException e) {
                         if (_log.shouldLog(Log.INFO))
                             _log.info("Failed fetch", e);
                         errors++;
@@ -658,7 +656,7 @@ public class Reseeder {
          *  @return count of routerinfos successfully fetched
          *  @since 0.9.14
          **/
-        public int reseedSU3(URL seedURL, boolean echoStatus) {
+        public int reseedSU3(URI seedURL, boolean echoStatus) {
             return reseedSU3OrZip(seedURL, true, echoStatus);
         }
 
@@ -672,7 +670,7 @@ public class Reseeder {
          *  @return count of routerinfos successfully fetched
          *  @since 0.9.19
          **/
-        public int reseedZip(URL seedURL, boolean echoStatus) {
+        public int reseedZip(URI seedURL, boolean echoStatus) {
             return reseedSU3OrZip(seedURL, false, echoStatus);
         }
 
@@ -686,7 +684,7 @@ public class Reseeder {
          *  @return count of routerinfos successfully fetched
          *  @since 0.9.19
          **/
-        private int reseedSU3OrZip(URL seedURL, boolean isSU3, boolean echoStatus) {
+        private int reseedSU3OrZip(URI seedURL, boolean isSU3, boolean echoStatus) {
             int fetched = 0;
             int errors = 0;
             File contentRaw = null;
@@ -868,7 +866,7 @@ public class Reseeder {
             if (ourHash != null && DataHelper.eq(hash, ourHash.getData()))
                 return false;
 
-            URL url = new URL(seedURL + (seedURL.endsWith("/") ? "" : "/") + ROUTERINFO_PREFIX + peer + ROUTERINFO_SUFFIX);
+            URI url = new URI(seedURL + (seedURL.endsWith("/") ? "" : "/") + ROUTERINFO_PREFIX + peer + ROUTERINFO_SUFFIX);
 
             byte data[] = readURL(url);
             if (data == null || data.length <= 0)
@@ -877,7 +875,7 @@ public class Reseeder {
         }
 
         /** @return null on error */
-        private byte[] readURL(URL url) throws IOException {
+        private byte[] readURL(URI url) throws IOException {
             ByteArrayOutputStream baos = new ByteArrayOutputStream(4*1024);
             EepGet get;
             boolean ssl = url.toString().startsWith("https");
@@ -922,7 +920,7 @@ public class Reseeder {
          *  @return null on error
          *  @since 0.9.14
          */
-        private File fetchURL(URL url) throws IOException {
+        private File fetchURL(URI url) throws IOException {
             File out = new File(_context.getTempDir(), "reseed-" + _context.random().nextInt() + ".tmp");
             EepGet get;
             boolean ssl = url.toString().startsWith("https");
diff --git a/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java b/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java
index 87f751bdcb725ceb2ba8786f3f5a245c01f2124f..ea800858bc93bfe779cd6825ea5fb87268ee8868 100644
--- a/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java
+++ b/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java
@@ -156,6 +156,11 @@ public class ProfileOrganizer {
      * Blocking if a reorganize is happening.
      */
     public PeerProfile getProfile(Hash peer) {
+        if (peer.equals(_us)) {
+            if (_log.shouldWarn())
+                _log.warn("Who wanted our own profile?", new Exception("I did"));
+            return null;
+        }
         getReadLock();
         try {
             return locked_getProfile(peer);
@@ -168,6 +173,11 @@ public class ProfileOrganizer {
      * @since 0.8.12
      */
     public PeerProfile getProfileNonblocking(Hash peer) {
+        if (peer.equals(_us)) {
+            if (_log.shouldWarn())
+                _log.warn("Who wanted our own profile?", new Exception("I did"));
+            return null;
+        }
         if (tryReadLock()) {
             try {
                 return locked_getProfile(peer);
@@ -184,6 +194,11 @@ public class ProfileOrganizer {
         if (profile == null) return null;
 
         Hash peer = profile.getPeer();
+        if (peer.equals(_us)) {
+            if (_log.shouldWarn())
+                _log.warn("Who added our own profile?", new Exception("I did"));
+            return null;
+        }
 
         if (_log.shouldLog(Log.DEBUG))
             _log.debug("New profile created for " + peer);
diff --git a/router/java/src/net/i2p/router/peermanager/ProfilePersistenceHelper.java b/router/java/src/net/i2p/router/peermanager/ProfilePersistenceHelper.java
index 305d0f343454323036ee3f1c51371f544866a5ec..af9cd33aa929e3bd646eef1e28a8d16194ed5e49 100644
--- a/router/java/src/net/i2p/router/peermanager/ProfilePersistenceHelper.java
+++ b/router/java/src/net/i2p/router/peermanager/ProfilePersistenceHelper.java
@@ -301,7 +301,7 @@ class ProfilePersistenceHelper {
                 _log.debug("Loaded the profile for " + peer.toBase64() + " from " + file.getName());
             
             return profile;
-        } catch (Exception e) {
+        } catch (IOException e) {
             if (_log.shouldLog(Log.WARN))
                 _log.warn("Error loading properties from " + file.getAbsolutePath(), e);
             file.delete();
@@ -369,7 +369,7 @@ class ProfilePersistenceHelper {
                 return null;
             Hash h = Hash.create(b);
             return h;
-        } catch (Exception dfe) {
+        } catch (RuntimeException dfe) {
             _log.warn("Invalid base64 [" + key + "]", dfe);
             return null;
         }
diff --git a/router/java/src/net/i2p/router/startup/RebuildRouterInfoJob.java b/router/java/src/net/i2p/router/startup/RebuildRouterInfoJob.java
index ef0826cf5fbcb99525f687249bd0319c581260ed..a3302ae408d5e4d5fef9e2e48d2aab59dbc2ba5d 100644
--- a/router/java/src/net/i2p/router/startup/RebuildRouterInfoJob.java
+++ b/router/java/src/net/i2p/router/startup/RebuildRouterInfoJob.java
@@ -98,7 +98,13 @@ class RebuildRouterInfoJob extends JobImpl {
                     KeyData kd = LoadRouterInfoJob.readKeyData(keyFile, keyFile2);
                     info = new RouterInfo();
                     info.setIdentity(kd.routerIdentity);
-                } catch (Exception e) {
+                } catch (DataFormatException e) {
+                    _log.log(Log.CRIT, "Error reading in the key data from " + keyFile.getAbsolutePath(), e);
+                    keyFile.delete();
+                    keyFile2.delete();
+                    rebuildRouterInfo(alreadyRunning);
+                    return;
+                } catch (IOException e) {
                     _log.log(Log.CRIT, "Error reading in the key data from " + keyFile.getAbsolutePath(), e);
                     keyFile.delete();
                     keyFile2.delete();
diff --git a/router/java/src/net/i2p/router/startup/WorkingDir.java b/router/java/src/net/i2p/router/startup/WorkingDir.java
index 8e3a19c93fdfe3a3d984687326a7f97b9c63df82..a7ca9f3303045343be4e4c5c4db3fd0f9406c61a 100644
--- a/router/java/src/net/i2p/router/startup/WorkingDir.java
+++ b/router/java/src/net/i2p/router/startup/WorkingDir.java
@@ -211,7 +211,7 @@ public class WorkingDir {
             String[] files = dir.list();
             if (files == null)
                 return false;
-            String migrated[] = MIGRATE_BASE.split(",");
+            String migrated[] = DataHelper.split(MIGRATE_BASE, ",");
             for (String file: files) {
                 for (int i = 0; i < migrated.length; i++) {
                     if (file.equals(migrated[i]))
@@ -282,7 +282,7 @@ public class WorkingDir {
 
     private static boolean migrate(String list, File olddir, File todir) {
         boolean rv = true;
-        String files[] = list.split(",");
+        String files[] = DataHelper.split(list, ",");
         for (int i = 0; i < files.length; i++) {
             File from = new File(olddir, files[i]);
             if (!copy(from, todir)) {
diff --git a/router/java/src/net/i2p/router/time/Zones.java b/router/java/src/net/i2p/router/time/Zones.java
index 85d5e388e4569da46e7b43265a1b1cd71c2ede0e..d04c67408ee7f191e7781faccf255f0a48b7fbc1 100644
--- a/router/java/src/net/i2p/router/time/Zones.java
+++ b/router/java/src/net/i2p/router/time/Zones.java
@@ -10,6 +10,7 @@ import java.util.Locale;
 import java.util.Map;
 
 import net.i2p.I2PAppContext;
+import net.i2p.data.DataHelper;
 import net.i2p.router.transport.GeoIP;
 
 /**
@@ -105,7 +106,7 @@ class Zones {
                 try {
                     if (line.charAt(0) == '#')
                         continue;
-                    String[] s = line.split(",");
+                    String[] s = DataHelper.split(line, ",");
                     String ucContinent = s[1].toUpperCase(Locale.US).trim();
                     String zone = _continentToZone.get(ucContinent);
                     if (zone == null)
diff --git a/router/java/src/net/i2p/router/transport/GeoIP.java b/router/java/src/net/i2p/router/transport/GeoIP.java
index 3213be64ee12d86ef19e1b3a249f5a1163e4e5aa..6a204016428903afc4a4a8089e7f856a101a0cdc 100644
--- a/router/java/src/net/i2p/router/transport/GeoIP.java
+++ b/router/java/src/net/i2p/router/transport/GeoIP.java
@@ -17,6 +17,7 @@ import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import net.i2p.I2PAppContext;
+import net.i2p.data.DataHelper;
 import net.i2p.data.Hash;
 import net.i2p.router.Router;
 import net.i2p.router.RouterContext;
@@ -209,7 +210,7 @@ public class GeoIP {
                     if (line.charAt(0) == '#') {
                         continue;
                     }
-                    String[] s = line.split(",");
+                    String[] s = DataHelper.split(line, ",");
                     String lc = s[0].toLowerCase(Locale.US);
                     _codeToName.put(lc, s[1]);
                     _codeCache.put(lc, lc);
@@ -274,7 +275,7 @@ public class GeoIP {
                     if (buf.charAt(0) == '#') {
                         continue;
                     }
-                    String[] s = buf.split(",");
+                    String[] s = DataHelper.split(buf, ",");
                     long ip1 = Long.parseLong(s[0]);
                     long ip2 = Long.parseLong(s[1]);
                     while (idx < search.length && search[idx].longValue() < ip1) {
diff --git a/router/java/src/net/i2p/router/transport/GeoIPv6.java b/router/java/src/net/i2p/router/transport/GeoIPv6.java
index b93e3f004f9536c471811a90cd553ea353c51c9d..af3120480a46607c77feddc03b36c2b3d72a3232 100644
--- a/router/java/src/net/i2p/router/transport/GeoIPv6.java
+++ b/router/java/src/net/i2p/router/transport/GeoIPv6.java
@@ -170,7 +170,7 @@ class GeoIPv6 {
                         if (buf.charAt(0) == '#') {
                             continue;
                         }
-                        String[] s = buf.split(",");
+                        String[] s = DataHelper.split(buf, ",");
                         String ips1 = s[0].replace("\"", "").trim();
                         String ips2 = s[1].replace("\"", "").trim();
                         byte[] ip1 = InetAddress.getByName(ips1).getAddress();
diff --git a/router/java/src/net/i2p/router/transport/UPnP.java b/router/java/src/net/i2p/router/transport/UPnP.java
index 26c7610b5f41c26e4062de8a2e9c83ff205aa95a..58cc32c75b905100c54e88555a47b97d3e560dfd 100644
--- a/router/java/src/net/i2p/router/transport/UPnP.java
+++ b/router/java/src/net/i2p/router/transport/UPnP.java
@@ -4,9 +4,9 @@
 package net.i2p.router.transport;
 
 import java.net.InetAddress;
-import java.net.MalformedURLException;
 import java.net.UnknownHostException;
-import java.net.URL;
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -224,7 +224,7 @@ class UPnP extends ControlPoint implements DeviceChangeListener, EventListener {
 		boolean ignore = false;
 		String toIgnore = _context.getProperty(PROP_IGNORE);
 		if (toIgnore != null) {
-			String[] ignores = toIgnore.split("[,; \r\n\t]");
+			String[] ignores = DataHelper.split(toIgnore, "[,; \r\n\t]");
 			for (int i = 0; i < ignores.length; i++) {
 				if (ignores[i].equals(udn)) {
 					ignore = true;
@@ -476,7 +476,7 @@ class UPnP extends ControlPoint implements DeviceChangeListener, EventListener {
 		ServiceStateTable table;
 		try {
 			table = serv.getServiceStateTable();
-		} catch (Exception e) {
+		} catch (RuntimeException e) {
 			// getSCPDNode() returns null,
 			// NPE at org.cybergarage.upnp.Service.getServiceStateTable(Service.java:526)
 			sb.append(" : no state");
@@ -823,17 +823,17 @@ class UPnP extends ControlPoint implements DeviceChangeListener, EventListener {
 		String him = _router.getURLBase();
 		if (him != null && him.length() > 0) {
 			try {
-				URL url = new URL(him);
+				URI url = new URI(him);
 				hisIP = url.getHost();
-			} catch (MalformedURLException mue) {}
+			} catch (URISyntaxException use) {}
 		}
 		if (hisIP == null) {
 			him = _router.getLocation();
 			if (him != null && him.length() > 0) {
 				try {
-					URL url = new URL(him);
+					URI url = new URI(him);
 					hisIP = url.getHost();
-				} catch (MalformedURLException mue) {}
+				} catch (URISyntaxException use) {}
 			}
 		}
 		if (hisIP == null)
diff --git a/router/java/src/net/i2p/router/transport/UPnPManager.java b/router/java/src/net/i2p/router/transport/UPnPManager.java
index 9d7a6918a0d16fb358c08022cc4def4b7fa1813c..cd9963b06751f6044861fbe21abc75fbfacb8ff5 100644
--- a/router/java/src/net/i2p/router/transport/UPnPManager.java
+++ b/router/java/src/net/i2p/router/transport/UPnPManager.java
@@ -88,7 +88,7 @@ class UPnPManager {
                 _isRunning = _upnp.runPlugin();
                 if (_log.shouldLog(Log.INFO))
                     _log.info("UPnP runPlugin took " + (_context.clock().now() - b));
-            } catch (Exception e) {
+            } catch (RuntimeException e) {
                 // NPE in UPnP (ticket #728), can't let it bring us down
                 if (!_errorLogged) {
                     _log.error("UPnP error, please report", e);
diff --git a/router/java/src/net/i2p/router/transport/ntcp/EventPumper.java b/router/java/src/net/i2p/router/transport/ntcp/EventPumper.java
index 053d4a3730dd4ebbd72ee55d93536ca1c2cfe864..dcc2226c4ef6be6d5e27b94b2d26fd12f95cd582 100644
--- a/router/java/src/net/i2p/router/transport/ntcp/EventPumper.java
+++ b/router/java/src/net/i2p/router/transport/ntcp/EventPumper.java
@@ -335,7 +335,7 @@ class EventPumper implements Runnable {
                             con.close();
                             key.cancel();
                         }
-                    } catch (Exception ke) {
+                    } catch (IOException ke) {
                         _log.error("Error closing key " + key + " on pumper shutdown", ke);
                     }
                 }
@@ -344,7 +344,7 @@ class EventPumper implements Runnable {
                 if (_log.shouldLog(Log.DEBUG))
                     _log.debug("Closing down the event pumper with no selection keys remaining");
             }
-        } catch (Exception e) {
+        } catch (IOException e) {
             _log.error("Error closing keys on pumper shutdown", e);
         }
         _wantsConRegister.clear();
diff --git a/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java b/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java
index 703ee5050a3b6d9314dfe9af3944fb7247c29b52..37504a111a936291f2557a5a101db1f587b80947 100644
--- a/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java
+++ b/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java
@@ -463,7 +463,7 @@ class NTCPConnection implements Closeable {
             _transport.afterSend(msg, successful, allowRequeue, msg.getLifetime());
             if (_consecutiveBacklog > 10) { // waaay too backlogged
                 boolean wantsWrite = false;
-                try { wantsWrite = ( (_conKey.interestOps() & SelectionKey.OP_WRITE) != 0); } catch (Exception e) {}
+                try { wantsWrite = ( (_conKey.interestOps() & SelectionKey.OP_WRITE) != 0); } catch (RuntimeException e) {}
                 if (_log.shouldLog(Log.WARN)) {
 		    int blocks = _writeBufs.size();
                     _log.warn("Too backlogged for too long (" + _consecutiveBacklog + " messages for " + DataHelper.formatDuration(queueTime()) + ", sched? " + wantsWrite + ", blocks: " + blocks + ") sending to " + _remotePeer.calculateHash());
@@ -521,7 +521,7 @@ class NTCPConnection implements Closeable {
                           + ", wantsWrite? " + (0 != (_conKey.interestOps()&SelectionKey.OP_WRITE))
                           + ", currentOut set? " + currentOutboundSet
 			  + ", writeBufs: " + writeBufs + " on " + toString());
-                } catch (Exception e) {}  // java.nio.channels.CancelledKeyException
+                } catch (RuntimeException e) {}  // java.nio.channels.CancelledKeyException
             }
             //_context.statManager().addRateData("ntcp.sendBacklogTime", queueTime);
             return true;
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 8e608ab136f938d1439864dc1eaecb0dbf828904..60976d285b199c4551c5f01422e9be6decbf8161 100644
--- a/router/java/src/net/i2p/router/transport/udp/ACKSender.java
+++ b/router/java/src/net/i2p/router/transport/udp/ACKSender.java
@@ -148,7 +148,7 @@ class ACKSender implements Runnable {
                         try {
                             // bulk operations may throw an exception
                             _peersToACK.addAll(notYet);
-                        } catch (Exception e) {}
+                        } catch (RuntimeException e) {}
                         if (_log.shouldLog(Log.DEBUG))
                             _log.debug("sleeping, pending size = " + notYet.size());
                         notYet.clear();
diff --git a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java
index 89076f9ce34d48726df5b412e0b1b5b3584a939a..77fdd78ba1367f1909dfa1a23a73e70af0bf383c 100644
--- a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java
+++ b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java
@@ -29,6 +29,7 @@ import net.i2p.router.util.DecayingBloomFilter;
 import net.i2p.util.Addresses;
 import net.i2p.util.I2PThread;
 import net.i2p.util.Log;
+import net.i2p.util.VersionComparator;
 
 /**
  * Coordinate the establishment of new sessions - both inbound and outbound.
@@ -126,6 +127,19 @@ class EstablishmentManager {
     /** for the DSM and or netdb store */
     private static final int DATA_MESSAGE_TIMEOUT = 10*1000;
     
+    /**
+     * Java I2P has always parsed the length of the extended options field,
+     * but i2pd hasn't recognized it until this release.
+     * No matter, the options weren't defined until this release anyway.
+     *
+**********************************************************************************************************
+     * FIXME 0.9.23 for testing, change to 0.9.24 for release
+     *
+     */
+    private static final String VERSION_ALLOW_EXTENDED_OPTIONS = "0.9.23";
+    private static final String PROP_DISABLE_EXT_OPTS = "i2np.udp.disableExtendedOptions";
+
+
     public EstablishmentManager(RouterContext ctx, UDPTransport transport) {
         _context = ctx;
         _log = ctx.logManager().getLog(EstablishmentManager.class);
@@ -356,8 +370,16 @@ class EstablishmentManager {
                         _transport.failed(msg, "Peer has bad key, cannot establish");
                         return;
                     }
+                    boolean allowExtendedOptions = VersionComparator.comp(toRouterInfo.getVersion(),
+                                                                          VERSION_ALLOW_EXTENDED_OPTIONS) >= 0
+                                                   && !_context.getBooleanProperty(PROP_DISABLE_EXT_OPTS);
+                    // w/o ext options, it's always 'requested', no need to set
+                    // don't ask if they are indirect
+                    boolean requestIntroduction = allowExtendedOptions && !isIndirect &&
+                                                  _transport.introducersMaybeRequired();
                     state = new OutboundEstablishState(_context, maybeTo, to,
-                                                       toIdentity,
+                                                       toIdentity, allowExtendedOptions,
+                                                       requestIntroduction,
                                                        sessionKey, addr, _transport.getDHFactory());
                     OutboundEstablishState oldState = _outboundStates.putIfAbsent(to, state);
                     boolean isNew = oldState == null;
@@ -477,7 +499,9 @@ class EstablishmentManager {
             // Don't offer to relay to privileged ports.
             // Only offer for an IPv4 session.
             // TODO if already we have their RI, only offer if they need it (no 'C' cap)
-            if (_transport.canIntroduce() && state.getSentPort() >= 1024 &&
+            // if extended options, only if they asked for it
+            if (state.isIntroductionRequested() &&
+                _transport.canIntroduce() && state.getSentPort() >= 1024 &&
                 state.getSentIP().length == 4) {
                 // ensure > 0
                 long tag = 1 + _context.random().nextLong(MAX_TAG_VALUE);
diff --git a/router/java/src/net/i2p/router/transport/udp/InboundEstablishState.java b/router/java/src/net/i2p/router/transport/udp/InboundEstablishState.java
index 590aa29ed24523b226ee993a6e1c6638ef19b7f3..332282c24d55a54ddf3151d9fd8d0615b912fcdf 100644
--- a/router/java/src/net/i2p/router/transport/udp/InboundEstablishState.java
+++ b/router/java/src/net/i2p/router/transport/udp/InboundEstablishState.java
@@ -62,6 +62,8 @@ class InboundEstablishState {
     private final Queue<OutNetMessage> _queuedMessages;
     // count for backoff
     private int _createdSentCount;
+    // default true
+    private boolean _introductionRequested = true;
     
     public enum InboundState {
         /** nothin known yet */
@@ -150,6 +152,12 @@ class InboundEstablishState {
         if (_bobIP == null)
             _bobIP = new byte[req.readIPSize()];
         req.readIP(_bobIP, 0);
+        byte[] ext = req.readExtendedOptions();
+        if (ext != null && ext.length >= UDPPacket.SESS_REQ_MIN_EXT_OPTIONS_LENGTH) {
+            _introductionRequested = (ext[1] & (byte) UDPPacket.SESS_REQ_EXT_FLAG_REQUEST_RELAY_TAG) != 0;
+            if (_log.shouldInfo())
+                _log.info("got sess req. w/ ext. options, need intro? " + _introductionRequested + ' ' + this);
+        }
         if (_log.shouldLog(Log.DEBUG))
             _log.debug("Receive sessionRequest, BobIP = " + Addresses.toString(_bobIP));
         if (_currentState == InboundState.IB_STATE_UNKNOWN)
@@ -160,6 +168,12 @@ class InboundEstablishState {
     public synchronized boolean sessionRequestReceived() { return _receivedX != null; }
     public synchronized byte[] getReceivedX() { return _receivedX; }
     public synchronized byte[] getReceivedOurIP() { return _bobIP; }
+    /**
+     *  True (default) if no extended options in session request,
+     *  or value of flag bit in the extended options.
+     *  @since 0.9.24
+     */
+    public synchronized boolean isIntroductionRequested() { return _introductionRequested; }
     
     /**
      *  Generates session key and mac key.
diff --git a/router/java/src/net/i2p/router/transport/udp/MessageReceiver.java b/router/java/src/net/i2p/router/transport/udp/MessageReceiver.java
index 1b6bb03c0bad03e658e3057312802d0b33e95abe..d2a3b0414b8d0ececb504af05e0b3952a2a46a2e 100644
--- a/router/java/src/net/i2p/router/transport/udp/MessageReceiver.java
+++ b/router/java/src/net/i2p/router/transport/udp/MessageReceiver.java
@@ -243,7 +243,7 @@ class MessageReceiver {
             }
             _context.messageHistory().droppedInboundMessage(state.getMessageId(), state.getFrom(), "error: " + ime.toString() + ": " + state.toString());
             return null;
-        } catch (Exception e) {
+        } catch (RuntimeException e) {
             // e.g. AIOOBE
             if (_log.shouldLog(Log.WARN))
                 _log.warn("Error handling a message: " + state, e);
diff --git a/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState.java b/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState.java
index 63497b5ea3a596f7f4733701d5ee13d5588d5d7b..d849bb836b31356675e0349e639706aa43ed458d 100644
--- a/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState.java
+++ b/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState.java
@@ -56,6 +56,8 @@ class OutboundEstablishState {
     private RemoteHostId _remoteHostId;
     private final RemoteHostId _claimedAddress;
     private final RouterIdentity _remotePeer;
+    private final boolean _allowExtendedOptions;
+    private final boolean _needIntroduction;
     private final SessionKey _introKey;
     private final Queue<OutNetMessage> _queuedMessages;
     private OutboundState _currentState;
@@ -107,12 +109,17 @@ class OutboundEstablishState {
      *  @param claimedAddress an IP/port based RemoteHostId, or null if unknown
      *  @param remoteHostId non-null, == claimedAddress if direct, or a hash-based one if indirect
      *  @param remotePeer must have supported sig type
+     *  @param allowExtenededOptions are we allowed to send extended options to Bob?
+     *  @param needIntroduction should we ask Bob to be an introducer for us?
+               ignored unless allowExtendedOptions is true
      *  @param introKey Bob's introduction key, as published in the netdb
      *  @param addr non-null
      */
     public OutboundEstablishState(RouterContext ctx, RemoteHostId claimedAddress,
                                   RemoteHostId remoteHostId,
-                                  RouterIdentity remotePeer, SessionKey introKey, UDPAddress addr,
+                                  RouterIdentity remotePeer, boolean allowExtendedOptions,
+                                  boolean needIntroduction,
+                                  SessionKey introKey, UDPAddress addr,
                                   DHSessionKeyBuilder.Factory dh) {
         _context = ctx;
         _log = ctx.logManager().getLog(OutboundEstablishState.class);
@@ -125,6 +132,8 @@ class OutboundEstablishState {
         }
         _claimedAddress = claimedAddress;
         _remoteHostId = remoteHostId;
+        _allowExtendedOptions = allowExtendedOptions;
+        _needIntroduction = needIntroduction;
         _remotePeer = remotePeer;
         _introKey = introKey;
         _queuedMessages = new LinkedBlockingQueue<OutNetMessage>();
@@ -157,6 +166,19 @@ class OutboundEstablishState {
 
     /** @return -1 if unset */
     public long getIntroNonce() { return _introductionNonce; }
+
+    /**
+     *  Are we allowed to send extended options to this peer?
+     *  @since 0.9.24
+     */
+    public boolean isExtendedOptionsAllowed() { return _allowExtendedOptions; }
+
+    /**
+     *  Should we ask this peer to be an introducer for us?
+     *  Ignored unless allowExtendedOptions is true
+     *  @since 0.9.24
+     */
+    public boolean needIntroduction() { return _needIntroduction; }
     
     /**
      *  Queue a message to be sent after the session is established.
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 3029f96c39de4fab539fb555a68a0aa3a7e6acd7..0f7e0ac80458f15576befbbc410033a36a98f214 100644
--- a/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java
+++ b/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java
@@ -57,10 +57,11 @@ to the various messages - a one byte flag and a four byte sending
 timestamp (*seconds* since the unix epoch).  The flag byte contains 
 the following bitfields:</p>
 <pre>
-  bits 0-3: payload type
-     bit 4: rekey?
-     bit 5: extended options included
-  bits 6-7: reserved
+Bit order: 76543210
+  bits 7-4: payload type
+     bit 3: rekey?
+     bit 2: extended options included
+  bits 1-0: reserved
 </pre>
 
 <p>If the rekey flag is set, 64 bytes of keying material follow the 
@@ -166,6 +167,19 @@ class PacketBuilder {
     private static final String PROP_PADDING = "i2np.udp.padding";
     private static final boolean DEFAULT_ENABLE_PADDING = true;
 
+    /**
+     *  The nine message types, 0-8, shifted to bits 7-4 for convenience
+     */
+    private static final byte SESSION_REQUEST_FLAG_BYTE = UDPPacket.PAYLOAD_TYPE_SESSION_REQUEST << 4;
+    private static final byte SESSION_CREATED_FLAG_BYTE = UDPPacket.PAYLOAD_TYPE_SESSION_CREATED << 4;
+    private static final byte SESSION_CONFIRMED_FLAG_BYTE = UDPPacket.PAYLOAD_TYPE_SESSION_CONFIRMED << 4;
+    private static final byte PEER_RELAY_REQUEST_FLAG_BYTE = UDPPacket.PAYLOAD_TYPE_RELAY_REQUEST << 4;
+    private static final byte PEER_RELAY_RESPONSE_FLAG_BYTE = UDPPacket.PAYLOAD_TYPE_RELAY_RESPONSE << 4;
+    private static final byte PEER_RELAY_INTRO_FLAG_BYTE = UDPPacket.PAYLOAD_TYPE_RELAY_INTRO << 4;
+    private static final byte DATA_FLAG_BYTE = UDPPacket.PAYLOAD_TYPE_DATA << 4;
+    private static final byte PEER_TEST_FLAG_BYTE = UDPPacket.PAYLOAD_TYPE_TEST << 4;
+    private static final byte SESSION_DESTROY_FLAG_BYTE = (byte) (UDPPacket.PAYLOAD_TYPE_SESSION_DESTROY << 4);
+    
     /**
      *  @param transport may be null for unit testing only
      */
@@ -332,7 +346,7 @@ class PacketBuilder {
         int availableForExplicitAcks = availableForAcks;
 
         // make the packet
-        UDPPacket packet = buildPacketHeader((byte)(UDPPacket.PAYLOAD_TYPE_DATA << 4));
+        UDPPacket packet = buildPacketHeader(DATA_FLAG_BYTE);
         DatagramPacket pkt = packet.getPacket();
         byte data[] = pkt.getData();
         int off = HEADER_SIZE;
@@ -573,7 +587,7 @@ class PacketBuilder {
      * @param ackBitfields list of ACKBitfield instances to either fully or partially ACK
      */
     public UDPPacket buildACK(PeerState peer, List<ACKBitfield> ackBitfields) {
-        UDPPacket packet = buildPacketHeader((byte)(UDPPacket.PAYLOAD_TYPE_DATA << 4));
+        UDPPacket packet = buildPacketHeader(DATA_FLAG_BYTE);
         DatagramPacket pkt = packet.getPacket();
         byte data[] = pkt.getData();
         int off = HEADER_SIZE;
@@ -667,12 +681,6 @@ class PacketBuilder {
         return packet;
     }
     
-    /** 
-     * full flag info for a sessionCreated message.  this can be fixed, 
-     * since we never rekey on startup, and don't need any extended options
-     */
-    private static final byte SESSION_CREATED_FLAG_BYTE = (UDPPacket.PAYLOAD_TYPE_SESSION_CREATED << 4);
-    
     /**
      * Build a new SessionCreated packet for the given peer, encrypting it 
      * as necessary.
@@ -768,12 +776,6 @@ class PacketBuilder {
         return packet;
     }
     
-    /** 
-     * full flag info for a sessionRequest message.  this can be fixed, 
-     * since we never rekey on startup, and don't need any extended options
-     */
-    private static final byte SESSION_REQUEST_FLAG_BYTE = (UDPPacket.PAYLOAD_TYPE_SESSION_REQUEST << 4);
-    
     /**
      * Build a new SessionRequest packet for the given peer, encrypting it 
      * as necessary.
@@ -781,10 +783,23 @@ class PacketBuilder {
      * @return ready to send packet, or null if there was a problem
      */
     public UDPPacket buildSessionRequestPacket(OutboundEstablishState state) {
-        UDPPacket packet = buildPacketHeader(SESSION_REQUEST_FLAG_BYTE);
+        int off = HEADER_SIZE;
+        byte[] options;
+        boolean ext = state.isExtendedOptionsAllowed();
+        if (ext) {
+            options = new byte[UDPPacket.SESS_REQ_MIN_EXT_OPTIONS_LENGTH];
+            boolean intro = state.needIntroduction();
+            if (intro)
+                options[1] = (byte) UDPPacket.SESS_REQ_EXT_FLAG_REQUEST_RELAY_TAG;
+            if (_log.shouldInfo())
+                _log.info("send sess req. w/ ext. options, need intro? " + intro + ' ' + state);
+            off += UDPPacket.SESS_REQ_MIN_EXT_OPTIONS_LENGTH + 1;
+        } else {
+            options = null;
+        }
+        UDPPacket packet = buildPacketHeader(SESSION_REQUEST_FLAG_BYTE, options);
         DatagramPacket pkt = packet.getPacket();
         byte data[] = pkt.getData();
-        int off = HEADER_SIZE;
 
         byte toIP[] = state.getSentIP();
         if (!_transport.isValid(toIP)) {
@@ -854,13 +869,6 @@ class PacketBuilder {
         return packets;
     }
 
-    
-    /** 
-     * full flag info for a sessionConfirmed message.  this can be fixed, 
-     * since we never rekey on startup, and don't need any extended options
-     */
-    private static final byte SESSION_CONFIRMED_FLAG_BYTE = (UDPPacket.PAYLOAD_TYPE_SESSION_CONFIRMED << 4);
-    
     /**
      * Build a new SessionConfirmed packet for the given peer
      * 
@@ -1018,7 +1026,7 @@ class PacketBuilder {
      *  @since 0.9.2
      */
     private UDPPacket buildSessionDestroyPacket(SessionKey cipherKey, SessionKey macKey, InetAddress addr, int port) {
-        UDPPacket packet = buildPacketHeader((byte)(UDPPacket.PAYLOAD_TYPE_SESSION_DESTROY << 4));
+        UDPPacket packet = buildPacketHeader(SESSION_DESTROY_FLAG_BYTE);
         int off = HEADER_SIZE;
         
         // no body in this message
@@ -1034,12 +1042,6 @@ class PacketBuilder {
         return packet;
     }
     
-    /** 
-     * full flag info for a peerTest message.  this can be fixed, 
-     * since we never rekey on test, and don't need any extended options
-     */
-    private static final byte PEER_TEST_FLAG_BYTE = (UDPPacket.PAYLOAD_TYPE_TEST << 4);
-
     /**
      * Build a packet as if we are Alice and we either want Bob to begin a 
      * peer test or Charlie to finish a peer test.
@@ -1197,12 +1199,6 @@ class PacketBuilder {
         packet.setMessageType(TYPE_TCB);
         return packet;
     }
-    
-    /** 
-     * full flag info for a relay request message.  this can be fixed, 
-     * since we never rekey on relay request, and don't need any extended options
-     */
-    private static final byte PEER_RELAY_REQUEST_FLAG_BYTE = (UDPPacket.PAYLOAD_TYPE_RELAY_REQUEST << 4);
 
     // specify these if we know what our external receive ip/port is and if its different
     // from what bob is going to think
@@ -1330,12 +1326,6 @@ class PacketBuilder {
         return packet;
     }
 
-    /** 
-     * full flag info for a relay intro message.  this can be fixed, 
-     * since we never rekey on relay request, and don't need any extended options
-     */
-    private static final byte PEER_RELAY_INTRO_FLAG_BYTE = (UDPPacket.PAYLOAD_TYPE_RELAY_INTRO << 4);
-    
     UDPPacket buildRelayIntro(RemoteHostId alice, PeerState charlie, UDPPacketReader.RelayRequestReader request) {
         UDPPacket packet = buildPacketHeader(PEER_RELAY_INTRO_FLAG_BYTE);
         DatagramPacket pkt = packet.getPacket();
@@ -1370,12 +1360,6 @@ class PacketBuilder {
         packet.setMessageType(TYPE_INTRO);
         return packet;
     }
-
-    /** 
-     * full flag info for a relay response message.  this can be fixed, 
-     * since we never rekey on relay response, and don't need any extended options
-     */
-    private static final byte PEER_RELAY_RESPONSE_FLAG_BYTE = (UDPPacket.PAYLOAD_TYPE_RELAY_RESPONSE << 4);
     
     UDPPacket buildRelayResponse(RemoteHostId alice, PeerState charlie, long nonce,
                                  SessionKey cipherKey, SessionKey macKey) {
@@ -1462,24 +1446,50 @@ class PacketBuilder {
     /**
      *  Create a new packet and add the flag byte and the time stamp.
      *  Caller should add data starting at HEADER_SIZE.
-     *  At this point, adding support for extended options and rekeying is unlikely,
-     *  but if we do, we'll have to change this.
+     *  Does not include extended options or rekeying.
      *
      *  @param flagByte contains type and flags
      *  @since 0.8.1
      */
     private UDPPacket buildPacketHeader(byte flagByte) {
+        return buildPacketHeader(flagByte, null);
+    }
+
+    /**
+     *  Create a new packet and add the flag byte and the time stamp.
+     *  Caller should add data starting at HEADER_SIZE.
+     *  (if extendedOptions != null, at HEADER_SIZE + 1 + extendedOptions.length)
+     *  Does not include rekeying.
+     *
+     *  @param flagByte contains type and flags
+     *  @param extendedOptions May be null. If non-null, we will add the associated flag here.
+     *                         255 bytes max.
+     *  @since 0.9.24
+     */
+    private UDPPacket buildPacketHeader(byte flagByte, byte[] extendedOptions) {
         UDPPacket packet = UDPPacket.acquire(_context, false);
         byte data[] = packet.getPacket().getData();
         Arrays.fill(data, 0, data.length, (byte)0x0);
         int off = UDPPacket.MAC_SIZE + UDPPacket.IV_SIZE;
         
         // header
+        if (extendedOptions != null)
+            flagByte |= UDPPacket.HEADER_FLAG_EXTENDED_OPTIONS;
         data[off] = flagByte;
         off++;
+        // Note, this is unsigned, so we're good until February 2106
         long now = (_context.clock().now() + 500) / 1000;
         DataHelper.toLong(data, off, 4, now);
-        // todo: add support for rekeying and extended options
+        // todo: add support for rekeying
+        // extended options
+        if (extendedOptions != null) {
+            off+= 4;
+            int len = extendedOptions.length;
+            if (len > 255)
+                throw new IllegalArgumentException();
+            data[off++] = (byte) len;
+            System.arraycopy(extendedOptions, 0, data, off, len);
+        }
         return packet;
     }
 
diff --git a/router/java/src/net/i2p/router/transport/udp/PacketHandler.java b/router/java/src/net/i2p/router/transport/udp/PacketHandler.java
index 736171b807fada6168c8d62c6b53b98f8c177de7..1f5f990bce0fe04fdd9c009dce629f8a1fc75fe2 100644
--- a/router/java/src/net/i2p/router/transport/udp/PacketHandler.java
+++ b/router/java/src/net/i2p/router/transport/udp/PacketHandler.java
@@ -215,7 +215,7 @@ class PacketHandler {
                     _state = 5;
                     handlePacket(_reader, packet);
                     _state = 6;
-                } catch (Exception e) {
+                } catch (RuntimeException e) {
                     _state = 7;
                     if (_log.shouldLog(Log.ERROR))
                         _log.error("Crazy error handling a packet: " + packet, e);
diff --git a/router/java/src/net/i2p/router/transport/udp/PacketPusher.java b/router/java/src/net/i2p/router/transport/udp/PacketPusher.java
index f28ee96450360ba618761ccf55d79563dba01313..5c770175c22baf102f32fab3084a9277cc6dcb91 100644
--- a/router/java/src/net/i2p/router/transport/udp/PacketPusher.java
+++ b/router/java/src/net/i2p/router/transport/udp/PacketPusher.java
@@ -43,7 +43,7 @@ class PacketPusher implements Runnable {
                          send(packets.get(i));
                     }
                 }
-            } catch (Exception e) {
+            } catch (RuntimeException e) {
                 _log.error("SSU Output Queue Error", e);
             }
         }
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 e2e1b3f592ae0926306ef2e50f754e467170ac6c..238ec16fd2a347862b58a25b7e30fb186414f11a 100644
--- a/router/java/src/net/i2p/router/transport/udp/UDPPacket.java
+++ b/router/java/src/net/i2p/router/transport/udp/UDPPacket.java
@@ -85,18 +85,38 @@ class UDPPacket implements CDQEntry {
     public static final int PAYLOAD_TYPE_RELAY_INTRO = 5;
     public static final int PAYLOAD_TYPE_DATA = 6;
     public static final int PAYLOAD_TYPE_TEST = 7;
-    public static final int MAX_PAYLOAD_TYPE = PAYLOAD_TYPE_TEST;
     /** @since 0.8.1 */
     public static final int PAYLOAD_TYPE_SESSION_DESTROY = 8;
+    public static final int MAX_PAYLOAD_TYPE = PAYLOAD_TYPE_SESSION_DESTROY;
     
+    // various flag fields for use in the header
+    /**
+     *  Defined in the spec from the beginning, Unused
+     *  @since 0.9.24
+     */
+    public static final byte HEADER_FLAG_REKEY = (1 << 3);
+    /**
+     *  Defined in the spec from the beginning, Used starting in 0.9.24
+     *  @since 0.9.24
+     */
+    public static final byte HEADER_FLAG_EXTENDED_OPTIONS = (1 << 2);
+
+    // Extended options for session request
+    public static final int SESS_REQ_MIN_EXT_OPTIONS_LENGTH = 2;
+    // bytes 0-1 are flags
+    /**
+     * set to 1 to request a session tag, i.e. we want him to be an introducer for us
+     */
+    public static final int SESS_REQ_EXT_FLAG_REQUEST_RELAY_TAG = 0x01;
+
     // various flag fields for use in the data packets
     public static final byte DATA_FLAG_EXPLICIT_ACK = (byte)(1 << 7);
     public static final byte DATA_FLAG_ACK_BITFIELDS = (1 << 6);
-    // unused
+    /** unused */
     public static final byte DATA_FLAG_ECN = (1 << 4);
     public static final byte DATA_FLAG_WANT_ACKS = (1 << 3);
     public static final byte DATA_FLAG_WANT_REPLY = (1 << 2);
-    // unused
+    /** unused */
     public static final byte DATA_FLAG_EXTENDED = (1 << 1);
     
     public static final byte BITFIELD_CONTINUATION = (byte)(1 << 7);
diff --git a/router/java/src/net/i2p/router/transport/udp/UDPPacketReader.java b/router/java/src/net/i2p/router/transport/udp/UDPPacketReader.java
index adacea03f906f7c38057a7330d6a4b82a6d50e00..c7c9d90cc0bdcf75030d31c9b961b1f7e01f26ef 100644
--- a/router/java/src/net/i2p/router/transport/udp/UDPPacketReader.java
+++ b/router/java/src/net/i2p/router/transport/udp/UDPPacketReader.java
@@ -67,33 +67,68 @@ class UDPPacketReader {
         return (_message[_payloadBeginOffset] & 0xFF) >>> 4;
     }
     
-    /** does this packet include rekeying data? */
-    public boolean readRekeying() {
-        return (_message[_payloadBeginOffset] & (1 << 3)) != 0;
+    /**
+     * Does this packet include rekeying data in the header?
+     * Unused, should always be false.
+     */
+    public boolean isRekeyingIncluded() {
+        return (_message[_payloadBeginOffset] & UDPPacket.HEADER_FLAG_REKEY) != 0;
     }
     
-    public boolean readExtendedOptionsIncluded() {
-        return (_message[_payloadBeginOffset] & (1 << 2)) != 0;
+    /**
+     * Does this packet include extended options in the header?
+     */
+    public boolean isExtendedOptionsIncluded() {
+        return (_message[_payloadBeginOffset] & UDPPacket.HEADER_FLAG_EXTENDED_OPTIONS) != 0;
     }
     
     /** @return seconds */
     public long readTimestamp() {
+        // Note, this is unsigned, so we're good until February 2106
         return DataHelper.fromLong(_message, _payloadBeginOffset + 1, 4);
     }
     
-    public void readKeyingMaterial(byte target[], int targetOffset) {
-        if (!readRekeying())
-            throw new IllegalStateException("This packet is not rekeying!");
-        System.arraycopy(_message, _payloadBeginOffset + 1 + 4, target, targetOffset, KEYING_MATERIAL_LENGTH);
+    /**
+     * Returns rekeying data (64 bytes), or null if none.
+     * Unused, should always return null.
+     *
+     * @deprecated unused
+     */
+    @Deprecated
+    public byte[] readKeyingMaterial() {
+        if (!isRekeyingIncluded())
+            return null;
+        byte[] rv = new byte[KEYING_MATERIAL_LENGTH];
+        System.arraycopy(_message, _payloadBeginOffset + 1 + 4, rv, 0, KEYING_MATERIAL_LENGTH);
+        return rv;
+    }
+    
+    /**
+     * Returns extended option data, 0-255 bytes, or null if none.
+     * Returned array does NOT include the length byte.
+     *
+     * @return extended options or null if none is included
+     * @since 0.9.24
+     */
+    public byte[] readExtendedOptions() {
+        if (!isExtendedOptionsIncluded())
+            return null;
+        int offset = _payloadBeginOffset + 1 + 4;
+        if (isRekeyingIncluded())
+            offset += KEYING_MATERIAL_LENGTH;
+        int optionsSize = _message[offset++] & 0xff;
+        byte[] rv = new byte[optionsSize];
+        System.arraycopy(_message, offset, rv, 0, optionsSize);
+        return rv;
     }
     
     /** index into the message where the body begins */
     private int readBodyOffset() {
         int offset = _payloadBeginOffset + 1 + 4;
-        if (readRekeying())
+        if (isRekeyingIncluded())
             offset += KEYING_MATERIAL_LENGTH;
-        if (readExtendedOptionsIncluded()) {
-            int optionsSize = (int)DataHelper.fromLong(_message, offset, 1);
+        if (isExtendedOptionsIncluded()) {
+            int optionsSize = _message[offset] & 0xff;
             offset += optionsSize + 1;
         }
         return offset;
@@ -142,8 +177,26 @@ class UDPPacketReader {
     
     /* ------- Begin Reader Classes ------- */
 
+    /**
+     * Base
+     *
+     * @since 0.9.24
+     */
+    public abstract class Reader {
+        /**
+         * Returns extended option data from the header, 0-255 bytes, or null if none.
+         * Returned array does NOT include the length byte.
+         *
+         * @return extended options or null if none is included
+         * @since 0.9.24
+         */
+        public byte[] readExtendedOptions() {
+            return UDPPacketReader.this.readExtendedOptions();
+        }
+    }
+
     /** Help read the SessionRequest payload */
-    public class SessionRequestReader {
+    public class SessionRequestReader extends Reader {
         public static final int X_LENGTH = 256;
         public void readX(byte target[], int targetOffset) {
             int readOffset = readBodyOffset();
@@ -152,20 +205,20 @@ class UDPPacketReader {
         
         public int readIPSize() {
             int offset = readBodyOffset() + X_LENGTH;
-            return (int)DataHelper.fromLong(_message, offset, 1);
+            return _message[offset] & 0xff;
         }
         
         /** what IP bob is reachable on */
         public void readIP(byte target[], int targetOffset) {
             int offset = readBodyOffset() + X_LENGTH;
-            int size = (int)DataHelper.fromLong(_message, offset, 1);
+            int size = _message[offset] & 0xff;
             offset++;
             System.arraycopy(_message, offset, target, targetOffset, size);
         }
     }
     
     /** Help read the SessionCreated payload */
-    public class SessionCreatedReader {
+    public class SessionCreatedReader extends Reader {
         public static final int Y_LENGTH = 256;
         public void readY(byte target[], int targetOffset) {
             int readOffset = readBodyOffset();
@@ -175,13 +228,13 @@ class UDPPacketReader {
         /** sizeof(IP) */
         public int readIPSize() {
             int offset = readBodyOffset() + Y_LENGTH;
-            return (int)DataHelper.fromLong(_message, offset, 1);
+            return _message[offset] & 0xff;
         }
         
         /** what IP do they think we are coming on? */
         public void readIP(byte target[], int targetOffset) {
             int offset = readBodyOffset() + Y_LENGTH;
-            int size = (int)DataHelper.fromLong(_message, offset, 1);
+            int size = _message[offset] & 0xff;
             offset++;
             System.arraycopy(_message, offset, target, targetOffset, size);
         }
@@ -220,7 +273,7 @@ class UDPPacketReader {
     }
     
     /** parse out the confirmed message */
-    public class SessionConfirmedReader {
+    public class SessionConfirmedReader extends Reader {
         /** which fragment is this? */
         public int readCurrentFragmentNum() {
             int readOffset = readBodyOffset();
@@ -273,7 +326,7 @@ class UDPPacketReader {
     }
     
     /** parse out the data message */
-    public class DataReader {
+    public class DataReader extends Reader {
 
         /**
          *  @return the data size, NOT including IP header, UDP header, IV, or MAC
@@ -307,7 +360,7 @@ class UDPPacketReader {
         public int readACKCount() {
             if (!readACKsIncluded()) return 0;
             int off = readBodyOffset() + 1;
-            return (int)DataHelper.fromLong(_message, off, 1);
+            return _message[off] & 0xff;
         }
 
         public long readACK(int index) {
@@ -322,12 +375,12 @@ class UDPPacketReader {
             if (!readACKBitfieldsIncluded()) return null;
             int off = readBodyOffset() + 1;
             if (readACKsIncluded()) {
-                int numACKs = (int)DataHelper.fromLong(_message, off, 1);
+                int numACKs = _message[off] & 0xff;
                 off++;
                 off += 4 * numACKs;
             }
             
-            int numBitfields = (int)DataHelper.fromLong(_message, off, 1);
+            int numBitfields = _message[off] & 0xff;
             off++;
             
             PacketACKBitfield rv[] = new PacketACKBitfield[numBitfields];
@@ -341,12 +394,12 @@ class UDPPacketReader {
         public int readFragmentCount() throws DataFormatException {
             int off = readBodyOffset() + 1;
             if (readACKsIncluded()) {
-                int numACKs = (int)DataHelper.fromLong(_message, off, 1);
+                int numACKs = _message[off] & 0xff;
                 off++;
                 off += 4 * numACKs;
             }
             if (readACKBitfieldsIncluded()) {
-                int numBitfields = (int)DataHelper.fromLong(_message, off, 1);
+                int numBitfields = _message[off] & 0xff;
                 off++;
 
                 for (int i = 0; i < numBitfields; i++) {
@@ -355,7 +408,7 @@ class UDPPacketReader {
                 }
             }
             if (readExtendedDataIncluded()) {
-                int size = (int)DataHelper.fromLong(_message, off, 1);
+                int size = _message[off] & 0xff;
                 off++;
                 off += size;
             }
@@ -397,12 +450,12 @@ class UDPPacketReader {
         private int getFragmentBegin(int fragmentNum) throws DataFormatException {
             int off = readBodyOffset() + 1;
             if (readACKsIncluded()) {
-                int numACKs = (int)DataHelper.fromLong(_message, off, 1);
+                int numACKs = _message[off] & 0xff;
                 off++;
                 off += 4 * numACKs;
             }
             if (readACKBitfieldsIncluded()) {
-                int numBitfields = (int)DataHelper.fromLong(_message, off, 1);
+                int numBitfields = _message[off] & 0xff;
                 off++;
 
                 PacketACKBitfield bf[] = new PacketACKBitfield[numBitfields];
@@ -412,7 +465,7 @@ class UDPPacketReader {
                 }
             }
             if (readExtendedDataIncluded()) {
-                int size = (int)DataHelper.fromLong(_message, off, 1);
+                int size = _message[off] & 0xff;
                 off++;
                 off += size;
             }
@@ -443,7 +496,7 @@ class UDPPacketReader {
             buf.append(" ");
             int off = readBodyOffset() + 1;
             if (readACKsIncluded()) {
-                int numACKs = (int)DataHelper.fromLong(_message, off, 1);
+                int numACKs = _message[off] & 0xff;
                 off++;
                 buf.append("with ACKs for ");
                 for (int i = 0; i < numACKs; i++) {
@@ -452,7 +505,7 @@ class UDPPacketReader {
                 }
             }
             if (readACKBitfieldsIncluded()) {
-                int numBitfields = (int)DataHelper.fromLong(_message, off, 1);
+                int numBitfields = _message[off] & 0xff;
                 off++;
                 buf.append("with partial ACKs for ");
 
@@ -468,7 +521,7 @@ class UDPPacketReader {
                 }
             }
             if (readExtendedDataIncluded()) {
-                int size = (int)DataHelper.fromLong(_message, off, 1);
+                int size = _message[off] & 0xff;
                 off++;
                 buf.append("with extended size of ");
                 buf.append(size);
@@ -476,7 +529,7 @@ class UDPPacketReader {
                 off += size;
             }
             
-            int numFragments = (int)DataHelper.fromLong(_message, off, 1);
+            int numFragments = _message[off] & 0xff;
             off++;
             buf.append("with fragmentCount of ");
             buf.append(numFragments);
@@ -506,8 +559,7 @@ class UDPPacketReader {
             buf.append(" payload: ");
                   
             int off = getFragmentBegin(0); // first fragment
-            off += 4; // messageId
-            off++; // fragment info
+            off += 4 + 1; // messageId + fragment info
             int size = ((int)DataHelper.fromLong(_message, off, 2)) & 0x3FFF;
             off += 2;
             buf.append(Base64.encode(_message, off, size));
@@ -610,7 +662,7 @@ class UDPPacketReader {
     }
     
     /** Help read the PeerTest payload */
-    public class PeerTestReader {
+    public class PeerTestReader extends Reader {
         private static final int NONCE_LENGTH = 4;
         
         public long readNonce() {
@@ -620,13 +672,13 @@ class UDPPacketReader {
         
         public int readIPSize() {
             int offset = readBodyOffset() + NONCE_LENGTH;
-            return (int)DataHelper.fromLong(_message, offset, 1);
+            return _message[offset] & 0xff;
         }
         
         /** what IP Alice is reachable on */
         public void readIP(byte target[], int targetOffset) {
             int offset = readBodyOffset() + NONCE_LENGTH;
-            int size = (int)DataHelper.fromLong(_message, offset, 1);
+            int size = _message[offset] & 0xff;
             offset++;
             System.arraycopy(_message, offset, target, targetOffset, size);
         }
@@ -634,7 +686,7 @@ class UDPPacketReader {
         /** what IP Alice is reachable on */
         public int readPort() {
             int offset = readBodyOffset() + NONCE_LENGTH;
-            int size = (int)DataHelper.fromLong(_message, offset, 1);
+            int size = _message[offset] & 0xff;
             offset++;
             offset += size; // skip the IP
             return (int)DataHelper.fromLong(_message, offset, 2);
@@ -643,16 +695,15 @@ class UDPPacketReader {
         /** what Alice's intro key is (if known - if unknown, the key is INVALID_KEY) */
         public void readIntroKey(byte target[], int targetOffset) {
             int offset = readBodyOffset() + NONCE_LENGTH;
-            int size = (int)DataHelper.fromLong(_message, offset, 1);
-            offset++;
+            int size = _message[offset] & 0xff;
+            offset += 1 + 2; // skip the size + port
             offset += size; // skip the IP
-            offset += 2; // skip the port
             System.arraycopy(_message, offset, target, targetOffset, SessionKey.KEYSIZE_BYTES);
         }
     }
     
     /** Help read the RelayRequest payload */
-    public class RelayRequestReader {
+    public class RelayRequestReader extends Reader {
         public long readTag() { 
             long rv = DataHelper.fromLong(_message, readBodyOffset(), 4); 
             if (_log.shouldLog(Log.DEBUG))
@@ -661,7 +712,7 @@ class UDPPacketReader {
         }
         public int readIPSize() {
             int offset = readBodyOffset() + 4;
-            int rv = (int)DataHelper.fromLong(_message, offset, 1);
+            int rv = _message[offset] & 0xff;
             if (_log.shouldLog(Log.DEBUG))
                 _log.debug("read alice ip size: " + rv);
             return rv;
@@ -670,7 +721,7 @@ class UDPPacketReader {
         /** what IP Alice is reachable on */
         public void readIP(byte target[], int targetOffset) {
             int offset = readBodyOffset() + 4;
-            int size = (int)DataHelper.fromLong(_message, offset, 1);
+            int size = _message[offset] & 0xff;
             offset++;
             System.arraycopy(_message, offset, target, targetOffset, size);
             if (_log.shouldLog(Log.DEBUG))
@@ -678,7 +729,7 @@ class UDPPacketReader {
         }
         public int readPort() {
             int offset = readBodyOffset() + 4;
-            offset += DataHelper.fromLong(_message, offset, 1);
+            offset += _message[offset] & 0xff;
             offset++;
             int rv = (int)DataHelper.fromLong(_message, offset, 2);
             if (_log.shouldLog(Log.DEBUG))
@@ -689,10 +740,9 @@ class UDPPacketReader {
         /** unused */
         public int readChallengeSize() {
             int offset = readBodyOffset() + 4;
-            offset += DataHelper.fromLong(_message, offset, 1);
-            offset++;
-            offset += 2;
-            int rv = (int)DataHelper.fromLong(_message, offset, 1);
+            offset += _message[offset] & 0xff;
+            offset += 1 + 2;
+            int rv = _message[offset] & 0xff;
             if (_log.shouldLog(Log.DEBUG))
                 _log.debug("read challenge size: " + rv);
             return rv;
@@ -701,10 +751,9 @@ class UDPPacketReader {
         /** unused */
         public void readChallengeSize(byte target[], int targetOffset) {
             int offset = readBodyOffset() + 4;
-            offset += DataHelper.fromLong(_message, offset, 1);
-            offset++;
-            offset += 2;
-            int sz = (int)DataHelper.fromLong(_message, offset, 1);
+            offset += _message[offset] & 0xff;
+            offset += 1 + 2;
+            int sz = _message[offset] & 0xff;
             offset++;
             System.arraycopy(_message, offset, target, targetOffset, sz);
             if (_log.shouldLog(Log.DEBUG))
@@ -712,10 +761,9 @@ class UDPPacketReader {
         }
         public void readAliceIntroKey(byte target[], int targetOffset) {
             int offset = readBodyOffset() + 4;
-            offset += DataHelper.fromLong(_message, offset, 1);
-            offset++;
-            offset += 2;
-            int sz = (int)DataHelper.fromLong(_message, offset, 1);
+            offset += _message[offset] & 0xff;
+            offset += 1 + 2;
+            int sz = _message[offset] & 0xff;
             offset++;
             offset += sz;
             System.arraycopy(_message, offset, target, targetOffset, SessionKey.KEYSIZE_BYTES);
@@ -725,10 +773,9 @@ class UDPPacketReader {
         }
         public long readNonce() {
             int offset = readBodyOffset() + 4;
-            offset += DataHelper.fromLong(_message, offset, 1);
-            offset++;
-            offset += 2;
-            int sz = (int)DataHelper.fromLong(_message, offset, 1);
+            offset += _message[offset] & 0xff;
+            offset += 1 + 2;
+            int sz = _message[offset] & 0xff;
             offset++;
             offset += sz;
             offset += SessionKey.KEYSIZE_BYTES;
@@ -740,22 +787,22 @@ class UDPPacketReader {
     }
     
     /** Help read the RelayIntro payload */
-    public class RelayIntroReader {
+    public class RelayIntroReader extends Reader {
         public int readIPSize() {
             int offset = readBodyOffset();
-            return (int)DataHelper.fromLong(_message, offset, 1);
+            return _message[offset] & 0xff;
         }
         
         /** what IP Alice is reachable on */
         public void readIP(byte target[], int targetOffset) {
             int offset = readBodyOffset();
-            int size = (int)DataHelper.fromLong(_message, offset, 1);
+            int size = _message[offset] & 0xff;
             offset++;
             System.arraycopy(_message, offset, target, targetOffset, size);
         }
         public int readPort() {
             int offset = readBodyOffset();
-            offset += DataHelper.fromLong(_message, offset, 1);
+            offset += _message[offset] & 0xff;
             offset++;
             return (int)DataHelper.fromLong(_message, offset, 2);
         }
@@ -763,19 +810,17 @@ class UDPPacketReader {
         /** unused */
         public int readChallengeSize() {
             int offset = readBodyOffset();
-            offset += DataHelper.fromLong(_message, offset, 1);
-            offset++;
-            offset += 2;
-            return (int)DataHelper.fromLong(_message, offset, 1);
+            offset += _message[offset] & 0xff;
+            offset += 1 + 2;
+            return _message[offset] & 0xff;
         }
 
         /** unused */
         public void readChallengeSize(byte target[], int targetOffset) {
             int offset = readBodyOffset();
-            offset += DataHelper.fromLong(_message, offset, 1);
-            offset++;
-            offset += 2;
-            int sz = (int)DataHelper.fromLong(_message, offset, 1);
+            offset += _message[offset] & 0xff;
+            offset += 1 + 2;
+            int sz = _message[offset] & 0xff;
             offset++;
             System.arraycopy(_message, offset, target, targetOffset, sz);
         }
@@ -783,22 +828,22 @@ class UDPPacketReader {
     
     
     /** Help read the RelayResponse payload */
-    public class RelayResponseReader {
+    public class RelayResponseReader extends Reader {
         public int readCharlieIPSize() {
             int offset = readBodyOffset();
-            return (int)DataHelper.fromLong(_message, offset, 1);
+            return _message[offset] & 0xff;
         }
         /** what IP charlie is reachable on */
         public void readCharlieIP(byte target[], int targetOffset) {
             int offset = readBodyOffset();
-            int size = (int)DataHelper.fromLong(_message, offset, 1);
+            int size = _message[offset] & 0xff;
             offset++;
             System.arraycopy(_message, offset, target, targetOffset, size);
         }
         /** what port charlie is reachable on */
         public int readCharliePort() {
             int offset = readBodyOffset();
-            offset += DataHelper.fromLong(_message, offset, 1);
+            offset += _message[offset] & 0xff;
             offset++;
             return (int)DataHelper.fromLong(_message, offset, 2);
         }
@@ -806,41 +851,36 @@ class UDPPacketReader {
         /** @deprecated unused */
         public int readAliceIPSize() {
             int offset = readBodyOffset();
-            offset += DataHelper.fromLong(_message, offset, 1);
-            offset++;
-            offset += 2;
-            return (int)DataHelper.fromLong(_message, offset, 1);
+            offset += _message[offset] & 0xff;
+            offset += 1 + 2;
+            return _message[offset] & 0xff;
         }
         /** @deprecated unused */
         public void readAliceIP(byte target[], int targetOffset) {
             int offset = readBodyOffset();
-            offset += DataHelper.fromLong(_message, offset, 1);
-            offset++;
-            offset += 2;
-            int sz = (int)DataHelper.fromLong(_message, offset, 1);
+            offset += _message[offset] & 0xff;
+            offset += 1 + 2;
+            int sz = _message[offset] & 0xff;
             offset++;
             System.arraycopy(_message, offset, target, targetOffset, sz);
         }
         /** @deprecated unused */
         public int readAlicePort() {
             int offset = readBodyOffset();
-            offset += DataHelper.fromLong(_message, offset, 1);
-            offset++;
-            offset += 2;
-            int sz = (int)DataHelper.fromLong(_message, offset, 1);
+            offset += _message[offset] & 0xff;
+            offset += 1 + 2;
+            int sz = _message[offset] & 0xff;
             offset++;
             offset += sz;
             return (int)DataHelper.fromLong(_message, offset, 2);
         }
         public long readNonce() {
             int offset = readBodyOffset();
-            offset += DataHelper.fromLong(_message, offset, 1);
-            offset++;
-            offset += 2;
-            int sz = (int)DataHelper.fromLong(_message, offset, 1);
-            offset++;
+            offset += _message[offset] & 0xff;
+            offset += 1 + 2;
+            int sz = _message[offset] & 0xff;
+            offset += 1 + 2; // sz + port
             offset += sz;
-            offset += 2;
             return DataHelper.fromLong(_message, offset, 4);
         }
     }
diff --git a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java
index edcfef26bbe711f38dfa8597517815899143489c..f6a94d7fb81a178b94ad90e9f09ec7d34a44b923 100644
--- a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java
+++ b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java
@@ -340,7 +340,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
 
         List<InetAddress> bindToAddrs = new ArrayList<InetAddress>(4);
         if (bindTo != null) {
-            String[] bta = bindTo.split("[,; \r\n\t]");
+            String[] bta = DataHelper.split(bindTo, "[,; \r\n\t]");
             for (int i = 0; i < bta.length; i++) {
                 String bt = bta[i];
                 if (bt.length() <= 0)
@@ -1896,7 +1896,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
         if (explicitAddressSpecified()) {
             host = _context.getProperty(PROP_EXTERNAL_HOST);
             if (host != null) {
-                String[] hosts = host.split("[,; \r\n\t]");
+                String[] hosts = DataHelper.split(host, "[,; \r\n\t]");
                 RouterAddress rv = null;
                 for (int i = 0; i < hosts.length; i++) {
                     String h = hosts[i];
@@ -2216,6 +2216,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
                 if (_log.shouldLog(Log.DEBUG))
                     _log.debug("Require introducers, because our status is " + status);
                 return true;
+
             default:
                 if (!allowDirectUDP()) {
                     if (_log.shouldLog(Log.DEBUG))
@@ -2226,6 +2227,30 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
         }
     }
     
+    /**
+     *  MIGHT we require introducers?
+     *  This is like introducersRequired, but if we aren't sure, this returns true.
+     *  Used only by EstablishmentManager.
+     *
+     *  @since 0.9.24
+     */
+    boolean introducersMaybeRequired() {
+        Status status = getReachabilityStatus();
+        switch (status) {
+            case REJECT_UNSOLICITED:
+            case DIFFERENT:
+            case IPV4_FIREWALLED_IPV6_OK:
+            case IPV4_FIREWALLED_IPV6_UNKNOWN:
+            case IPV4_UNKNOWN_IPV6_OK:
+            case IPV4_UNKNOWN_IPV6_FIREWALLED:
+            case UNKNOWN:
+                return true;
+
+            default:
+                return !allowDirectUDP();
+        }
+    }
+    
     /**
      *  For EstablishmentManager
      *  @since 0.9.3
diff --git a/router/java/src/net/i2p/router/tunnel/BuildReplyHandler.java b/router/java/src/net/i2p/router/tunnel/BuildReplyHandler.java
index 2196a851e6e543256b1af596b4e31e09e76e2e0c..8514d6db1ac0c7444617ce0453a2671f8c7a6041 100644
--- a/router/java/src/net/i2p/router/tunnel/BuildReplyHandler.java
+++ b/router/java/src/net/i2p/router/tunnel/BuildReplyHandler.java
@@ -120,7 +120,7 @@ public class BuildReplyHandler {
             return -1;
         } else {
             SimpleByteCache.release(h);
-            int rv = (int)DataHelper.fromLong(data, TunnelBuildReplyMessage.RECORD_SIZE - 1, 1);
+            int rv = data[TunnelBuildReplyMessage.RECORD_SIZE - 1] & 0xff;
             if (log.shouldLog(Log.DEBUG))
                 log.debug(reply.getUniqueId() + ": Verified: " + rv + " for record " + recordNum + "/" + hop);
             return rv;
diff --git a/router/java/src/net/i2p/router/tunnel/FragmentHandler.java b/router/java/src/net/i2p/router/tunnel/FragmentHandler.java
index 6e0d0675fcbd0740534edb0924b743570f4d33d2..eef1327123e575ac198e1d2bf46cd27539afceef 100644
--- a/router/java/src/net/i2p/router/tunnel/FragmentHandler.java
+++ b/router/java/src/net/i2p/router/tunnel/FragmentHandler.java
@@ -340,7 +340,7 @@ class FragmentHandler {
             offset += 4;
         }
         if (extended) {
-            int extendedSize = (int)DataHelper.fromLong(preprocessed, offset, 1);
+            int extendedSize = preprocessed[offset] & 0xff;
             offset++;
             offset += extendedSize; // we don't interpret these yet, but skip them for now
         }
diff --git a/router/java/src/net/i2p/router/tunnel/HopConfig.java b/router/java/src/net/i2p/router/tunnel/HopConfig.java
index 0e317cd79e896e4130405daa1f849be440f6135c..3045c337154de826def3c0dbcb2e74b06bf813ab 100644
--- a/router/java/src/net/i2p/router/tunnel/HopConfig.java
+++ b/router/java/src/net/i2p/router/tunnel/HopConfig.java
@@ -51,12 +51,14 @@ public class HopConfig {
     public void setReceiveTunnelId(byte id[]) { _receiveTunnelId = id; }
     public void setReceiveTunnelId(TunnelId id) { _receiveTunnelId = DataHelper.toLong(4, id.getTunnelId()); }
     
-    /** what is the previous peer in the tunnel (if any)? */
+    /** what is the previous peer in the tunnel (null if gateway) */
     public Hash getReceiveFrom() { return _receiveFrom; }
     public void setReceiveFrom(Hash from) { _receiveFrom = from; }
     
-    /** what is the next tunnel ID we are sending to? */
+    /** what is the next tunnel ID we are sending to? (null if endpoint) */
     public byte[] getSendTunnelId() { return _sendTunnelId; }
+
+    /** what is the next tunnel we are sending to? (null if endpoint) */
     public TunnelId getSendTunnel() { 
         if (_sendTunnel == null)
             _sendTunnel = getTunnel(_sendTunnelId); 
@@ -71,7 +73,7 @@ public class HopConfig {
             return new TunnelId(DataHelper.fromLong(id, 0, id.length));
     }
     
-    /** what is the next peer in the tunnel (if any)? */
+    /** what is the next peer in the tunnel (null if endpoint) */
     public Hash getSendTo() { return _sendTo; }
     public void setSendTo(Hash to) { _sendTo = to; }
     
diff --git a/router/java/src/net/i2p/router/tunnel/TunnelCreatorConfig.java b/router/java/src/net/i2p/router/tunnel/TunnelCreatorConfig.java
index 9340477c6f4efb37d15e63ff17e3318cc9f20c57..758d2096b99a5042f0201577c772075239207ff0 100644
--- a/router/java/src/net/i2p/router/tunnel/TunnelCreatorConfig.java
+++ b/router/java/src/net/i2p/router/tunnel/TunnelCreatorConfig.java
@@ -30,16 +30,32 @@ public class TunnelCreatorConfig implements TunnelInfo {
     private long _replyMessageId;
     private final boolean _isInbound;
     private int _messagesProcessed;
-    private volatile long _verifiedBytesTransferred;
+    private long _verifiedBytesTransferred;
     private boolean _failed;
     private int _failures;
     private boolean _reused;
     private int _priority;
+    //private static final int THROUGHPUT_COUNT = 3;
+    // Fastest 1 minute throughput, in bytes per minute, ordered with fastest first.
+    //private final double _peakThroughput[] = new double[THROUGHPUT_COUNT];
+    private long _peakThroughputCurrentTotal;
+    private long _peakThroughputLastCoallesce = System.currentTimeMillis();
+    // Make configurable? - but can't easily get to pool options from here
+    private static final int MAX_CONSECUTIVE_TEST_FAILURES = 3;
+    private static final SimpleDateFormat _fmt = new SimpleDateFormat("HH:mm:ss", Locale.UK);
     
+    /** 
+     * For exploratory only (null destination)
+     * @param length 1 minimum (0 hop is length 1)
+     */
     public TunnelCreatorConfig(RouterContext ctx, int length, boolean isInbound) {
         this(ctx, length, isInbound, null);
     }
 
+    /** 
+     * @param length 1 minimum (0 hop is length 1)
+     * @param destination null for exploratory
+     */
     public TunnelCreatorConfig(RouterContext ctx, int length, boolean isInbound, Hash destination) {
         _context = ctx;
         if (length <= 0)
@@ -131,10 +147,14 @@ public class TunnelCreatorConfig implements TunnelInfo {
     public void setReplyMessageId(long id) { _replyMessageId = id; }
     
     /** take note of a message being pumped through this tunnel */
-    public void incrementProcessedMessages() { _messagesProcessed++; }
-    public int getProcessedMessagesCount() { return _messagesProcessed; }
+    public synchronized void incrementProcessedMessages() { _messagesProcessed++; }
+    public synchronized int getProcessedMessagesCount() { return _messagesProcessed; }
 
-    public void incrementVerifiedBytesTransferred(int bytes) { 
+    /**
+     *  This calls profile manager tunnelDataPushed1m() for each peer
+     *  @return null for exploratory
+     */
+    public synchronized void incrementVerifiedBytesTransferred(int bytes) { 
         _verifiedBytesTransferred += bytes; 
         _peakThroughputCurrentTotal += bytes;
         long now = System.currentTimeMillis();
@@ -144,38 +164,34 @@ public class TunnelCreatorConfig implements TunnelInfo {
             double normalized = tot * 60d*1000d / timeSince;
             _peakThroughputLastCoallesce = now;
             _peakThroughputCurrentTotal = 0;
-            if (_context != null)
-                for (int i = 0; i < _peers.length; i++)
+            if (_context != null) {
+                // skip ourselves
+                int start = _isInbound ? 0 : 1;
+                int end = _isInbound ? _peers.length - 1 : _peers.length;
+                for (int i = start; i < end; i++) {
                     _context.profileManager().tunnelDataPushed1m(_peers[i], (int)normalized);
+                }
+            }
         }
     }
 
-    public long getVerifiedBytesTransferred() { return _verifiedBytesTransferred; }
+    public synchronized long getVerifiedBytesTransferred() { return _verifiedBytesTransferred; }
 
-    private static final int THROUGHPUT_COUNT = 3;
-    /** 
-     * fastest 1 minute throughput, in bytes per minute, ordered with fastest
-     * first.
-     */
-    private final double _peakThroughput[] = new double[THROUGHPUT_COUNT];
-    private volatile long _peakThroughputCurrentTotal;
-    private volatile long _peakThroughputLastCoallesce = System.currentTimeMillis();
-    public double getPeakThroughputKBps() { 
+/**** unused
+    public synchronized double getPeakThroughputKBps() { 
         double rv = 0;
         for (int i = 0; i < THROUGHPUT_COUNT; i++)
             rv += _peakThroughput[i];
         rv /= (60d*1024d*THROUGHPUT_COUNT);
         return rv;
     }
-    public void setPeakThroughputKBps(double kBps) {
+
+    public synchronized void setPeakThroughputKBps(double kBps) {
         _peakThroughput[0] = kBps*60*1024;
         //for (int i = 0; i < THROUGHPUT_COUNT; i++)
         //    _peakThroughput[i] = kBps*60;
     }
-    
-    
-    // Make configurable? - but can't easily get to pool options from here
-    private static final int MAX_CONSECUTIVE_TEST_FAILURES = 3;
+****/
     
     /**
      * The tunnel failed a test, so (maybe) stop using it
@@ -264,11 +280,10 @@ public class TunnelCreatorConfig implements TunnelInfo {
         return buf.toString();
     }
     
-    private static final SimpleDateFormat _fmt = new SimpleDateFormat("HH:mm:ss", Locale.UK);
-
     private String getExpirationString() {
         return format(_expiration);
     }
+
     static String format(long date) {
         Date d = new Date(date);
         synchronized (_fmt) {
diff --git a/router/java/src/net/i2p/router/tunnel/TunnelDispatcher.java b/router/java/src/net/i2p/router/tunnel/TunnelDispatcher.java
index 8e4d433353c6a010143a7429d7821f0199d5a6b8..6c80375ed826bc9fbce81f2bf513c9030b29aaff 100644
--- a/router/java/src/net/i2p/router/tunnel/TunnelDispatcher.java
+++ b/router/java/src/net/i2p/router/tunnel/TunnelDispatcher.java
@@ -457,7 +457,8 @@ public class TunnelDispatcher implements Service {
                 _inboundGateways.remove(recvId);
             } else {
                 // update stats based off getCompleteCount() + getFailedCount()
-                for (int i = 0; i < cfg.getLength(); i++) {
+                // skip last hop (us)
+                for (int i = 0; i < cfg.getLength() - 1; i++) {
                     Hash peer = cfg.getPeer(i);
                     PeerProfile profile = _context.profileOrganizer().getProfile(peer);
                     if (profile != null) {
diff --git a/router/java/src/net/i2p/router/tunnel/pool/BuildHandler.java b/router/java/src/net/i2p/router/tunnel/pool/BuildHandler.java
index 5b2bdfb2a1a7f2a0d00a05cf152c49da4aa4d844..0848bbe1bbe251fa116a814f677e6b93c7737edd 100644
--- a/router/java/src/net/i2p/router/tunnel/pool/BuildHandler.java
+++ b/router/java/src/net/i2p/router/tunnel/pool/BuildHandler.java
@@ -213,7 +213,7 @@ class BuildHandler implements Runnable {
         while (_isRunning && !_manager.isShutdown()) {
             try {
                 handleInboundRequest();
-            } catch (Exception e) {
+            } catch (RuntimeException e) {
                 _log.log(Log.CRIT, "B0rked in the tunnel handler", e);
             }
         }
@@ -795,7 +795,8 @@ class BuildHandler implements Runnable {
             cfg.setIVKey(req.readIVKey());
             cfg.setLayerKey(req.readLayerKey());
             if (isInGW) {
-                cfg.setReceiveFrom(null);
+                // default
+                //cfg.setReceiveFrom(null);
             } else {
                 if (state.fromHash != null) {
                     cfg.setReceiveFrom(state.fromHash);
@@ -808,8 +809,9 @@ class BuildHandler implements Runnable {
             }
             cfg.setReceiveTunnelId(DataHelper.toLong(4, ourId));
             if (isOutEnd) {
-                cfg.setSendTo(null);
-                cfg.setSendTunnelId(null);
+                // default
+                //cfg.setSendTo(null);
+                //cfg.setSendTunnelId(null);
             } else {
                 cfg.setSendTo(nextPeer);
                 cfg.setSendTunnelId(DataHelper.toLong(4, nextId));
diff --git a/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java b/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java
index a1dae253c88a97260ac93a8cb764b3b8a61e963b..aaf24387cc738bfb6ea8819180175f442332e7dd 100644
--- a/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java
+++ b/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java
@@ -1168,7 +1168,7 @@ public class TunnelPool {
             int j = peers.size() - 1 - i;
             cfg.setPeer(j, peers.get(i));
             HopConfig hop = cfg.getConfig(j);
-            hop.setCreation(_context.clock().now());
+            hop.setCreation(now);
             hop.setExpiration(expiration);
             hop.setIVKey(_context.keyGenerator().generateSessionKey());
             hop.setLayerKey(_context.keyGenerator().generateSessionKey());
diff --git a/router/java/src/net/i2p/router/util/EventLog.java b/router/java/src/net/i2p/router/util/EventLog.java
index 1cad4b9b4700d7ff866ff2b1e5196d675f4e1c01..14f829dbef2a98568383eb14ec9827c90470f1a0 100644
--- a/router/java/src/net/i2p/router/util/EventLog.java
+++ b/router/java/src/net/i2p/router/util/EventLog.java
@@ -13,6 +13,7 @@ import java.util.SortedMap;
 import java.util.TreeMap;
 
 import net.i2p.I2PAppContext;
+import net.i2p.data.DataHelper;
 import net.i2p.util.SecureFileOutputStream;
 
 /**
@@ -125,7 +126,7 @@ public class EventLog {
             String line = null;
             while ( (line = br.readLine()) != null) {
                 try {
-                    String[] s = line.split(" ", 3);
+                    String[] s = DataHelper.split(line, " ", 3);
                     if (!s[1].equals(event))
                         continue;
                     long time = Long.parseLong(s[0]);
@@ -167,7 +168,7 @@ public class EventLog {
             String line = null;
             while ( (line = br.readLine()) != null) {
                 try {
-                    String[] s = line.split(" ", 2);
+                    String[] s = DataHelper.split(line, " ", 2);
                     if (s.length < 2)
                         continue;
                     long time = Long.parseLong(s[0]);