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 7c9d101214838a4549684b83d95ef78bcd0e9652..a570de2d06a28cc6e3a67fcbf8e29de0f747b90a 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java
@@ -329,14 +329,18 @@ public class I2PSnarkServlet extends Default {
 
         // Opera and text-mode browsers: no   and no input type=image values submitted
         String ua = req.getHeader("User-Agent");
-        boolean isDegraded = ua != null && (ua.startsWith("Opera") || ua.startsWith("Lynx") ||
+        boolean isDegraded = ua != null && (ua.startsWith("Lynx") ||
                                             ua.startsWith("ELinks") || ua.startsWith("Dillo"));
 
+        boolean noThinsp = isDegraded || ua.startsWith("Opera");
         if (_manager.util().connected()) {
             if (isDegraded)
                 out.write("<a href=\"/i2psnark/?action=StopAll&amp;nonce=" + _nonce + "\"><img title=\"");
-            else
-                out.write("<input type=\"image\" name=\"action\" value=\"StopAll\" title=\"");
+            else {
+                // http://www.onenaught.com/posts/382/firefox-4-change-input-type-image-only-submits-x-and-y-not-name
+                //out.write("<input type=\"image\" name=\"action\" value=\"StopAll\" title=\"");
+                out.write("<input type=\"image\" name=\"action_StopAll\" value=\"foo\" title=\"");
+            }
             out.write(_("Stop all torrents and the I2P tunnel"));
             out.write("\" src=\"" + _imgPath + "stop_all.png\" alt=\"");
             out.write(_("Stop All"));
@@ -347,7 +351,7 @@ public class I2PSnarkServlet extends Default {
             if (isDegraded)
                 out.write("<a href=\"/i2psnark/?action=StartAll&amp;nonce=" + _nonce + "\"><img title=\"");
             else
-                out.write("<input type=\"image\" name=\"action\" value=\"StartAll\" title=\"");
+                out.write("<input type=\"image\" name=\"action_StartAll\" value=\"foo\" title=\"");
             out.write(_("Start all torrents and the I2P tunnel"));
             out.write("\" src=\"" + _imgPath + "start_all.png\" alt=\"");
             out.write(_("Start All"));
@@ -362,7 +366,7 @@ public class I2PSnarkServlet extends Default {
             Snark snark = (Snark)snarks.get(i);
             boolean showDebug = "2".equals(peerParam);
             boolean showPeers = showDebug || "1".equals(peerParam) || Base64.encode(snark.meta.getInfoHash()).equals(peerParam);
-            displaySnark(out, snark, uri, i, stats, showPeers, isDegraded, showDebug);
+            displaySnark(out, snark, uri, i, stats, showPeers, isDegraded, noThinsp, showDebug);
         }
 
         if (snarks.isEmpty()) {
@@ -404,8 +408,19 @@ public class I2PSnarkServlet extends Default {
     private void processRequest(HttpServletRequest req) {
         String action = req.getParameter("action");
         if (action == null) {
-            _manager.addMessage("No action specified");
-            return;
+            // http://www.onenaught.com/posts/382/firefox-4-change-input-type-image-only-submits-x-and-y-not-name
+            Map params = req.getParameterMap();
+            for (Object o : params.keySet()) {
+                String key = (String) o;
+                if (key.startsWith("action_") && key.endsWith(".x")) {
+                    action = key.substring(0, key.length() - 2).substring(7);
+                    break;
+                }
+            }
+            if (action == null) {
+                _manager.addMessage("No action specified");
+                return;
+            }
         }
         // sadly, Opera doesn't send value with input type=image, so we have to use GET there
         //if (!"POST".equals(req.getMethod())) {
@@ -698,7 +713,7 @@ public class I2PSnarkServlet extends Default {
     private static final int MAX_DISPLAYED_FILENAME_LENGTH = 50;
     private static final int MAX_DISPLAYED_ERROR_LENGTH = 43;
     private void displaySnark(PrintWriter out, Snark snark, String uri, int row, long stats[], boolean showPeers,
-                              boolean isDegraded, boolean showDebug) throws IOException {
+                              boolean isDegraded, boolean noThinsp, boolean showDebug) throws IOException {
         String filename = snark.torrent;
         File f = new File(filename);
         filename = f.getName(); // the torrent may be the canonical name, so lets just grab the local name
@@ -758,11 +773,11 @@ public class I2PSnarkServlet extends Default {
             if (isRunning && curPeers > 0 && !showPeers)
                 statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + "trackererror.png\" title=\"" + err + "\"></td><td class=\"snarkTorrentStatus " + rowClass + "\">" + _("Tracker Error") +
                                ": <a href=\"" + uri + "?p=" + Base64.encode(snark.meta.getInfoHash()) + "\">" +
-                               curPeers + thinsp(isDegraded) +
+                               curPeers + thinsp(noThinsp) +
                                ngettext("1 peer", "{0} peers", knownPeers) + "</a>";
             else if (isRunning)
                 statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + "trackererror.png\" title=\"" + err + "\"></td><td class=\"snarkTorrentStatus " + rowClass + "\">" + _("Tracker Error") +
-                               ": " + curPeers + thinsp(isDegraded) +
+                               ": " + curPeers + thinsp(noThinsp) +
                                ngettext("1 peer", "{0} peers", knownPeers);
             else {
                 if (err.length() > MAX_DISPLAYED_ERROR_LENGTH)
@@ -774,11 +789,11 @@ public class I2PSnarkServlet extends Default {
             if (isRunning && curPeers > 0 && !showPeers)
                 statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + "seeding.png\" ></td><td class=\"snarkTorrentStatus " + rowClass + "\">" + _("Seeding") +
                                ": <a href=\"" + uri + "?p=" + Base64.encode(snark.meta.getInfoHash()) + "\">" +
-                               curPeers + thinsp(isDegraded) +
+                               curPeers + thinsp(noThinsp) +
                                ngettext("1 peer", "{0} peers", knownPeers) + "</a>";
             else if (isRunning)
                 statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + "seeding.png\" ></td><td class=\"snarkTorrentStatus " + rowClass + "\">" + _("Seeding") +
-                               ": " + curPeers + thinsp(isDegraded) +
+                               ": " + curPeers + thinsp(noThinsp) +
                                ngettext("1 peer", "{0} peers", knownPeers);
             else
                 statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + "complete.png\" ></td><td class=\"snarkTorrentStatus " + rowClass + "\">" + _("Complete");
@@ -786,24 +801,24 @@ public class I2PSnarkServlet extends Default {
             if (isRunning && curPeers > 0 && downBps > 0 && !showPeers)
                 statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + "downloading.png\" ></td><td class=\"snarkTorrentStatus " + rowClass + "\">" + _("OK") +
                                ": <a href=\"" + uri + "?p=" + Base64.encode(snark.meta.getInfoHash()) + "\">" +
-                               curPeers + thinsp(isDegraded) +
+                               curPeers + thinsp(noThinsp) +
                                ngettext("1 peer", "{0} peers", knownPeers) + "</a>";
             else if (isRunning && curPeers > 0 && downBps > 0)
                 statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + "downloading.png\" ></td><td class=\"snarkTorrentStatus " + rowClass + "\">" + _("OK") +
-                               ": " + curPeers + thinsp(isDegraded) +
+                               ": " + curPeers + thinsp(noThinsp) +
                                ngettext("1 peer", "{0} peers", knownPeers);
             else if (isRunning && curPeers > 0 && !showPeers)
                 statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + "stalled.png\" ></td><td class=\"snarkTorrentStatus " + rowClass + "\">" + _("Stalled") +
                                ": <a href=\"" + uri + "?p=" + Base64.encode(snark.meta.getInfoHash()) + "\">" +
-                               curPeers + thinsp(isDegraded) +
+                               curPeers + thinsp(noThinsp) +
                                ngettext("1 peer", "{0} peers", knownPeers) + "</a>";
             else if (isRunning && curPeers > 0)
                 statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + "stalled.png\" ></td><td class=\"snarkTorrentStatus " + rowClass + "\">" + _("Stalled") +
-                               ": " + curPeers + thinsp(isDegraded) +
+                               ": " + curPeers + thinsp(noThinsp) +
                                ngettext("1 peer", "{0} peers", knownPeers);
             else if (isRunning && knownPeers > 0)
                 statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + "nopeers.png\" ></td><td class=\"snarkTorrentStatus " + rowClass + "\">" + _("No Peers") +
-                               ": 0" + thinsp(isDegraded) + knownPeers ;
+                               ": 0" + thinsp(noThinsp) + knownPeers ;
             else if (isRunning)
                 statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + "nopeers.png\" ></td><td class=\"snarkTorrentStatus " + rowClass + "\">" + _("No Peers");
             else
@@ -880,7 +895,7 @@ public class I2PSnarkServlet extends Default {
         out.write("</td>\n\t");
         out.write("<td align=\"right\" class=\"snarkTorrentDownloaded " + rowClass + "\">");
         if (remaining > 0)
-            out.write(formatSize(total-remaining) + thinsp(isDegraded) + formatSize(total));
+            out.write(formatSize(total-remaining) + thinsp(noThinsp) + formatSize(total));
         else
             out.write(formatSize(total)); // 3GB
         out.write("</td>\n\t");
@@ -905,7 +920,7 @@ public class I2PSnarkServlet extends Default {
             if (isDegraded)
                 out.write("<a href=\"/i2psnark/?action=Stop_" + b64 + "&amp;nonce=" + _nonce + "\"><img title=\"");
             else
-                out.write("<input type=\"image\" name=\"action\" value=\"Stop_" + b64 + "\" title=\"");
+                out.write("<input type=\"image\" name=\"action_Stop_" + b64 + "\" value=\"foo\" title=\"");
             out.write(_("Stop the torrent"));
             out.write("\" src=\"" + _imgPath + "stop.png\" alt=\"");
             out.write(_("Stop"));
@@ -917,7 +932,7 @@ public class I2PSnarkServlet extends Default {
                 if (isDegraded)
                     out.write("<a href=\"/i2psnark/?action=Start_" + b64 + "&amp;nonce=" + _nonce + "\"><img title=\"");
                 else
-                    out.write("<input type=\"image\" name=\"action\" value=\"Start_" + b64 + "\" title=\"");
+                    out.write("<input type=\"image\" name=\"action_Start_" + b64 + "\" value=\"foo\" title=\"");
                 out.write(_("Start the torrent"));
                 out.write("\" src=\"" + _imgPath + "start.png\" alt=\"");
                 out.write(_("Start"));
@@ -929,7 +944,7 @@ public class I2PSnarkServlet extends Default {
             if (isDegraded)
                 out.write("<a href=\"/i2psnark/?action=Remove_" + b64 + "&amp;nonce=" + _nonce + "\"><img title=\"");
             else
-                out.write("<input type=\"image\" name=\"action\" value=\"Remove_" + b64 + "\" title=\"");
+                out.write("<input type=\"image\" name=\"action_Remove_" + b64 + "\" value=\"foo\" title=\"");
             out.write(_("Remove the torrent from the active list, deleting the .torrent file"));
             out.write("\" onclick=\"if (!confirm('");
             // Can't figure out how to escape double quotes inside the onclick string.
@@ -946,7 +961,7 @@ public class I2PSnarkServlet extends Default {
             if (isDegraded)
                 out.write("<a href=\"/i2psnark/?action=Delete_" + b64 + "&amp;nonce=" + _nonce + "\"><img title=\"");
             else
-                out.write("<input type=\"image\" name=\"action\" value=\"Delete_" + b64 + "\" title=\"");
+                out.write("<input type=\"image\" name=\"action_Delete_" + b64 + "\" value=\"foo\" title=\"");
             out.write(_("Delete the .torrent file and the associated data file(s)"));
             out.write("\" onclick=\"if (!confirm('");
             // Can't figure out how to escape double quotes inside the onclick string.
@@ -1384,7 +1399,7 @@ public class I2PSnarkServlet extends Default {
     private static String urlify(String s) {
         StringBuilder buf = new StringBuilder(256);
         // browsers seem to work without doing this but let's be strict
-        String link = s.replace("&", "&amp;");
+        String link = s.replace("&", "&amp;").replace(" ", "%20");
         buf.append("<a href=\"").append(link).append("\">").append(link).append("</a>");
         return buf.toString();
     }
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java
index 4299b6d4788e9aa028858c666fd4981fa80ce12b..1f1f9e971d376c7843b3d08efca0cf9c37d8ba7b 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java
@@ -259,10 +259,9 @@ public class TunnelController implements Logging {
     
     /*
      *  Streamr client is a UDP server, use the listenPort field for targetPort
-     *  and the listenOnInterface field for the targetHost
      */
     private void startStreamrClient() {
-        String targetHost = getListenOnInterface();
+        String targetHost = getTargetHost();
         String targetPort = getListenPort();
         String dest = getTargetDestination();
         _tunnel.runStreamrClient(new String[] { targetHost, targetPort, dest }, this);
@@ -270,10 +269,9 @@ public class TunnelController implements Logging {
     
     /**
      *  Streamr server is a UDP client, use the targetPort field for listenPort
-     *  and the targetHost field for the listenOnInterface
      */
     private void startStreamrServer() {
-        String listenOn = getTargetHost();
+        String listenOn = getListenOnInterface();
         if ( (listenOn != null) && (listenOn.length() > 0) ) {
             _tunnel.runListenOn(new String[] { listenOn }, this);
         }
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 87beb689cb3022bf97268321896537c59db2cbef..2184b434f1f02aa968cc9f698e47b84b53a8b758 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java
@@ -12,6 +12,7 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Properties;
 import java.util.StringTokenizer;
+import java.util.Set;
 
 import net.i2p.data.Base64;
 import net.i2p.data.Destination;
@@ -22,6 +23,7 @@ import net.i2p.i2ptunnel.I2PTunnelHTTPClient;
 import net.i2p.i2ptunnel.I2PTunnelHTTPClientBase;
 import net.i2p.i2ptunnel.TunnelController;
 import net.i2p.i2ptunnel.TunnelControllerGroup;
+import net.i2p.util.Addresses;
 
 /**
  * Ugly little accessor for the edit page
@@ -314,6 +316,11 @@ public class EditBean extends IndexBean {
         return _context.isRouterContext();
     }
 
+    /** @since 0.8.3 */
+    public Set<String> interfaceSet() {
+        return Addresses.getAllAddresses();
+    }
+
     public String getI2CPHost(int tunnel) {
         if (_context.isRouterContext())
             return _("internal");
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 bb1a339c294140da226b3711aa5066c6265eda61..9787078894a7998b4fd3ba6a234ebc497690291b 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java
@@ -63,7 +63,6 @@ public class IndexBean {
     private String _proxyList;
     private String _port;
     private String _reachableBy;
-    private String _reachableByOther;
     private String _targetDestination;
     private String _targetHost;
     private String _targetPort;
@@ -432,10 +431,13 @@ public class IndexBean {
     
     public String getClientInterface(int tunnel) {
         TunnelController tun = getController(tunnel);
-        if (tun != null)
-            return tun.getListenOnInterface();
-        else
-            return "";
+        if (tun != null) {
+            if ("streamrclient".equals(tun.getType()))
+                return tun.getTargetHost();
+            else
+                return tun.getListenOnInterface();
+        } else
+            return "127.0.0.1";
     }
     
     public int getTunnelStatus(int tunnel) {
@@ -478,11 +480,38 @@ public class IndexBean {
         return rv != null ? rv : "";
     }
     
+    /**
+     * Call this to see if it is ok to linkify getServerTarget()
+     * @since 0.8.3
+     */
+    public boolean isServerTargetLinkValid(int tunnel) {
+        TunnelController tun = getController(tunnel);
+        return tun != null &&
+               "httpserver".equals(tun.getType()) &&
+               tun.getTargetHost() != null &&
+               tun.getTargetPort() != null;
+    }
+
+    /**
+     * @return valid host:port only if isServerTargetLinkValid() is true
+     */
     public String getServerTarget(int tunnel) {
         TunnelController tun = getController(tunnel);
-        if (tun != null)
-            return tun.getTargetHost() + ':' + tun.getTargetPort();
-        else
+        if (tun != null) {
+            String host;
+            if ("streamrserver".equals(tun.getType()))
+                host = tun.getListenOnInterface();
+            else
+                host = tun.getTargetHost();
+            String port = tun.getTargetPort();
+            if (host == null)
+                host = "<font color=\"red\">" + _("Host not set") + "</font>";
+            else if (host.indexOf(':') >= 0)
+                host = '[' + host + ']';
+            if (port == null)
+                port = "<font color=\"red\">" + _("Port not set") + "</font>";
+            return host + ':' + port;
+       }  else
             return "";
     }
     
@@ -575,19 +604,11 @@ public class IndexBean {
         _port = (port != null ? port.trim() : null);
     }
     /** 
-     * what interface should this client/httpclient/ircclient listen on (unless 
-     * overridden by the setReachableByOther() field)
+     * what interface should this client/httpclient/ircclient listen on
      */
     public void setReachableBy(String reachableBy) { 
         _reachableBy = (reachableBy != null ? reachableBy.trim() : null);
     }
-    /**
-     * If specified, defines the exact IP interface to listen for requests
-     * on (in the case of client/httpclient/ircclient tunnels)
-     */
-    public void setReachableByOther(String reachableByOther) { 
-        _reachableByOther = (reachableByOther != null ? reachableByOther.trim() : null);
-    }
     /** What peer does this client tunnel point at */
     public void setTargetDestination(String dest) { 
         _targetDestination = (dest != null ? dest.trim() : null);
@@ -891,17 +912,22 @@ public class IndexBean {
         Properties config = new Properties();
         updateConfigGeneric(config);
         
-        if (isClient(_type)) {
-            // generic client stuff
-            if (_port != null)
-                config.setProperty("listenPort", _port);
-            if (_reachableByOther != null)
-                config.setProperty("interface", _reachableByOther);
-            else if (_reachableBy != null)
+        if ((isClient(_type) && !"streamrclient".equals(_type)) || "streamrserver".equals(_type)) {
+            // streamrserver uses interface
+            if (_reachableBy != null)
                 config.setProperty("interface", _reachableBy);
             else
                 config.setProperty("interface", "");
+        } else {
+            // streamrclient uses targetHost
+            if (_targetHost != null)
+                config.setProperty("targetHost", _targetHost);
+        }
 
+        if (isClient(_type)) {
+            // generic client stuff
+            if (_port != null)
+                config.setProperty("listenPort", _port);
             config.setProperty("sharedClient", _sharedClient + "");
             for (String p : _booleanClientOpts)
                 config.setProperty("option." + p, "" + _booleanOptions.contains(p));
@@ -910,8 +936,6 @@ public class IndexBean {
                     config.setProperty("option." + p, _otherOptions.get(p));
         } else {
             // generic server stuff
-            if (_targetHost != null)
-                config.setProperty("targetHost", _targetHost);
             if (_targetPort != null)
                 config.setProperty("targetPort", _targetPort);
             for (String p : _booleanServerOpts)
@@ -940,9 +964,7 @@ public class IndexBean {
         if ("httpbidirserver".equals(_type)) {
             if (_port != null)
                 config.setProperty("listenPort", _port);
-            if (_reachableByOther != null)
-                config.setProperty("interface", _reachableByOther);
-            else if (_reachableBy != null)
+            if (_reachableBy != null)
                 config.setProperty("interface", _reachableBy);
             else if (_targetHost != null)
                 config.setProperty("interface", _targetHost);
diff --git a/apps/i2ptunnel/jsp/editClient.jsp b/apps/i2ptunnel/jsp/editClient.jsp
index f2b427542ba1c87d60310e99174a8692fb3634b3..cabfb2946479df5f7aba8cd4862c856e7d96cf7d 100644
--- a/apps/i2ptunnel/jsp/editClient.jsp
+++ b/apps/i2ptunnel/jsp/editClient.jsp
@@ -80,7 +80,7 @@
                 <label><%=intl._("Target")%>:</label>
          <% } else { %>
                 <label><%=intl._("Access Point")%>:</label>
-         <% } %>
+         <% } /* streamrclient */ %>
             </div>
             <div id="portField" class="rowItem">
                 <label for="port" accesskey="P">
@@ -95,46 +95,41 @@
                 </label>
                 <input type="text" size="6" maxlength="5" id="port" name="port" title="Access Port Number" value="<%=editBean.getClientPort(curTunnel)%>" class="freetext" />               
             </div>
-         <% String otherInterface = "";
-            String clientInterface = editBean.getClientInterface(curTunnel);
-            if ("streamrclient".equals(tunnelType)) {   
-                otherInterface = clientInterface;
-            } else { %>
             <div id="reachField" class="rowItem">
                 <label for="reachableBy" accesskey="r">
-                    <%=intl._("Reachable by")%>(<span class="accessKey">R</span>):
-                </label>
-                <select id="reachableBy" name="reachableBy" title="Valid IP for Client Access" class="selectbox">
-                  <%   if (!("127.0.0.1".equals(clientInterface)) &&
-                           !("0.0.0.0".equals(clientInterface)) &&
-                            (clientInterface != null) &&
-                            (clientInterface.trim().length() > 0)) {
-                            otherInterface = clientInterface;
-                       }
-                  %><option value="127.0.0.1"<%=("127.0.0.1".equals(clientInterface) ? " selected=\"selected\"" : "")%>><%=intl._("Locally (127.0.0.1)")%></option>
-                    <option value="0.0.0.0"<%=("0.0.0.0".equals(clientInterface) ? " selected=\"selected\"" : "")%>><%=intl._("Everyone (0.0.0.0)")%></option>
-                    <option value="other"<%=(!("".equals(otherInterface))    ? " selected=\"selected\"" : "")%>><%=intl._("LAN Hosts (Please specify your LAN address)")%></option>
-                </select>                
-            </div> 
-         <% } // streamrclient %>
-            <div id="otherField" class="rowItem">
-                <label for="reachableByOther" accesskey="O">
-         <% if ("streamrclient".equals(tunnelType)) { %>
-                    Host:
-                    <% String vvv = otherInterface;
-                       if (vvv == null || "".equals(vvv.trim())) {
+         <%
+            if ("streamrclient".equals(tunnelType)) {
+                       out.write("Host:");
+                       String targetHost = editBean.getTargetHost(curTunnel);
+                       if (targetHost == null || "".equals(targetHost.trim())) {
                            out.write(" <font color=\"red\">(");
                            out.write(intl._("required"));
                            out.write(")</font>");
                        }   
-                     %>
+          %>
+                </label>
+                <input type="text" size="20" id="targetHost" name="targetHost" title="Target Hostname or IP" value="<%=targetHost%>" class="freetext" />                
          <% } else { %>
-                    <%=intl._("Other")%>(<span class="accessKey">O</span>):
-         <% } %>
+                    <%=intl._("Reachable by")%>(<span class="accessKey">R</span>):
                 </label>
-                <input type="text" size="20" id="reachableByOther" name="reachableByOther" title="Alternative IP for Client Access" value="<%=otherInterface%>" class="freetext" />                
-            </div>
-                                            
+                <select id="reachableBy" name="reachableBy" title="IP for Client Access" class="selectbox">
+              <%
+                    String clientInterface = editBean.getClientInterface(curTunnel);
+                    for (String ifc : editBean.interfaceSet()) {
+                        out.write("<option value=\"");
+                        out.write(ifc);
+                        out.write('\"');
+                        if (ifc.equals(clientInterface))
+                            out.write(" selected=\"selected\"");
+                        out.write('>');
+                        out.write(ifc);
+                        out.write("</option>\n");
+                    }
+              %>
+                </select>                
+         <% } /* streamrclient */ %>
+            </div> 
+
             <div class="subdivider">
                 <hr />
             </div>
diff --git a/apps/i2ptunnel/jsp/editServer.jsp b/apps/i2ptunnel/jsp/editServer.jsp
index b0f870fb7aa5797730ea6a1bc6f38ff480b17ac2..4f45b86672c9f70d3d2c7ee8cd132175b713d92a 100644
--- a/apps/i2ptunnel/jsp/editServer.jsp
+++ b/apps/i2ptunnel/jsp/editServer.jsp
@@ -89,16 +89,14 @@
                 <label><%=intl._("Target")%>:</label>
          <% } %>
             </div>
+         <% if (!"streamrserver".equals(tunnelType)) { %>
             <div id="hostField" class="rowItem">
                 <label for="targetHost" accesskey="H">
-         <% if ("streamrserver".equals(tunnelType)) { %>
-                    <%=intl._("Reachable by")%>(<span class="accessKey">R</span>):
-         <% } else { %>
                     <%=intl._("Host")%>(<span class="accessKey">H</span>):
-         <% } %>
                 </label>
                 <input type="text" size="20" id="targetHost" name="targetHost" title="Target Hostname or IP" value="<%=editBean.getTargetHost(curTunnel)%>" class="freetext" />                
             </div>
+         <% } /* !streamrserver */ %>
             <div id="portField" class="rowItem">
                 <label for="targetPort" accesskey="P">
                     <%=intl._("Port")%>(<span class="accessKey">P</span>):
@@ -113,8 +111,7 @@
                 <input type="text" size="6" maxlength="5" id="targetPort" name="targetPort" title="Target Port Number" value="<%=editBean.getTargetPort(curTunnel)%>" class="freetext" />               
             </div>
             
-            <% if ("httpbidirserver".equals(tunnelType)) {
-            %>
+         <% if ("httpbidirserver".equals(tunnelType)) { %>
             <div class="subdivider">
                 <hr />
             </div>
@@ -134,32 +131,30 @@
               	 </label>
                  <input type="text" size="6" maxlength="5" id="port" name="port" title="Access Port Number" value="<%=editBean.getClientPort(curTunnel)%>" class="freetext" />
             </div>
-            <% String otherInterface = "";
-            String clientInterface = editBean.getClientInterface(curTunnel);
-            %>
+         <% } /* httpbidirserver */ %>
+         <% if ("httpbidirserver".equals(tunnelType) || "streamrserver".equals(tunnelType)) { %>
             <div id="reachField" class="rowItem">
                 <label for="reachableBy" accesskey="r">
                     <%=intl._("Reachable by")%>(<span class="accessKey">R</span>):
                 </label>
-                <select id="reachableBy" name="reachableBy" title="Valid IP for Client Access" class="selectbox">
-                  <%   if (!("127.0.0.1".equals(clientInterface)) &&
-                           !("0.0.0.0".equals(clientInterface)) &&
-                            (clientInterface != null) &&
-                            (clientInterface.trim().length() > 0)) {
-                            otherInterface = clientInterface;
-                       }
-                  %><option value="127.0.0.1"<%=("127.0.0.1".equals(clientInterface) ? " selected=\"selected\"" : "")%>><%=intl._("Locally (127.0.0.1)")%></option>
-                    <option value="0.0.0.0"<%=("0.0.0.0".equals(clientInterface) ? " selected=\"selected\"" : "")%>><%=intl._("Everyone (0.0.0.0)")%></option>
-                    <option value="other"<%=(!("".equals(otherInterface))    ? " selected=\"selected\"" : "")%>><%=intl._("LAN Hosts (Please specify your LAN address)")%></option>
+                <select id="reachableBy" name="reachableBy" title="IP for Client Access" class="selectbox">
+              <%
+                    String clientInterface = editBean.getClientInterface(curTunnel);
+                    for (String ifc : editBean.interfaceSet()) {
+                        out.write("<option value=\"");
+                        out.write(ifc);
+                        out.write('\"');
+                        if (ifc.equals(clientInterface))
+                            out.write(" selected=\"selected\"");
+                        out.write('>');
+                        out.write(ifc);
+                        out.write("</option>\n");
+                    }
+              %>
                 </select>                
-            </div> 
-            <div id="otherField" class="rowItem">
-                <label for="reachableByOther" accesskey="O">
-                    <%=intl._("Other")%>(<span class="accessKey">O</span>):
-                </label>
-                <input type="text" size="20" id="reachableByOther" name="reachableByOther" title="Alternative IP for Client Access" value="<%=otherInterface%>" class="freetext" />                
             </div>
-            <% } %>
+         <% } /* httpbidirserver || streamrserver */ %>
+
             <div class="subdivider">
                 <hr />
             </div>
@@ -302,7 +297,7 @@
             <div class="subdivider">
                 <hr />
             </div>
-         <% } // !streamrserver %>
+         <% } /* !streamrserver */ %>
 
             <div id="optionsField" class="rowItem">
                 <label><%=intl._("Router I2CP Address")%>:</label>
diff --git a/apps/i2ptunnel/jsp/index.jsp b/apps/i2ptunnel/jsp/index.jsp
index cf0d0067c39cf4551a8fe37a1dae821fa2cca46d..faf904de034f52e0ea2e26bd2e26291e9a754273 100644
--- a/apps/i2ptunnel/jsp/index.jsp
+++ b/apps/i2ptunnel/jsp/index.jsp
@@ -95,7 +95,7 @@
             <label><%=intl._("Points at")%>:</label>
             <span class="text">
         <%
-            if ("httpserver".equals(indexBean.getInternalType(curServer))) {
+            if (indexBean.isServerTargetLinkValid(curServer)) {
           %>
             <a href="http://<%=indexBean.getServerTarget(curServer)%>/" title="Test HTTP server, bypassing I2P"><%=indexBean.getServerTarget(curServer)%></a>
         <%
@@ -213,7 +213,18 @@
         </div>
         <div class="portField rowItem">
             <label><%=intl._("Port")%>:</label>
-            <span class="text"><%=indexBean.getClientPort(curClient)%></span>
+            <span class="text">
+         <%
+               String cPort= indexBean.getClientPort(curClient);
+               if ("".equals(cPort)) {
+                   out.write("<font color=\"red\">");
+                   out.write(intl._("Port not set"));
+                   out.write("</font>");
+               } else {
+                   out.write(cPort);
+               }
+          %>
+            </span>
         </div>
         <div class="typeField rowItem">
             <label><%=intl._("Type")%>:</label>
@@ -221,7 +232,19 @@
         </div>
         <div class="interfaceField rowItem">
             <label><%=intl._("Interface")%>:</label>
-            <span class="text"><%=indexBean.getClientInterface(curClient)%></span>
+            <span class="text">
+         <%
+               /* should only happen for streamr client */
+               String cHost= indexBean.getClientInterface(curClient);
+               if ("".equals(cHost)) {
+                   out.write("<font color=\"red\">");
+                   out.write(intl._("Hort not set"));
+                   out.write("</font>");
+               } else {
+                   out.write(cHost);
+               }
+          %>
+            </span>
         </div>
         <div class="statusField rowItem">
             <label><%=intl._("Status")%>:</label>
diff --git a/core/java/src/net/i2p/crypto/ElGamalAESEngine.java b/core/java/src/net/i2p/crypto/ElGamalAESEngine.java
index 11010f3124c775242ff24875ebefd604542f4b30..6160caf5b7a9243401ac5b6a166340649fcccb67 100644
--- a/core/java/src/net/i2p/crypto/ElGamalAESEngine.java
+++ b/core/java/src/net/i2p/crypto/ElGamalAESEngine.java
@@ -327,11 +327,12 @@ public class ElGamalAESEngine {
             //_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) +")");
-            byte hashval[] = new byte[Hash.HASH_LENGTH];
-            System.arraycopy(decrypted, cur, hashval, 0, Hash.HASH_LENGTH);
+            //byte hashval[] = new byte[Hash.HASH_LENGTH];
+            //System.arraycopy(decrypted, cur, hashval, 0, Hash.HASH_LENGTH);
+            //readHash = new Hash();
+            //readHash.setData(hashval);
+            readHash = Hash.create(decrypted, cur);
             cur += Hash.HASH_LENGTH;
-            readHash = new Hash();
-            readHash.setData(hashval);
             byte flag = decrypted[cur++];
             if (flag == 0x01) {
                 byte rekeyVal[] = new byte[SessionKey.KEYSIZE_BYTES];
diff --git a/core/java/src/net/i2p/crypto/ElGamalEngine.java b/core/java/src/net/i2p/crypto/ElGamalEngine.java
index ae3b0eb3addbe476498b019bbca8d75dc8d3bf30..b311d3c07915efa516ef6b4ea9040fcef8abc2a9 100644
--- a/core/java/src/net/i2p/crypto/ElGamalEngine.java
+++ b/core/java/src/net/i2p/crypto/ElGamalEngine.java
@@ -202,9 +202,10 @@ public class ElGamalEngine {
         }
 
         //ByteArrayInputStream bais = new ByteArrayInputStream(val, i, val.length - i);
-        byte hashData[] = new byte[Hash.HASH_LENGTH];
-        System.arraycopy(val, i + 1, hashData, 0, Hash.HASH_LENGTH);
-        Hash hash = new Hash(hashData);
+        //byte hashData[] = new byte[Hash.HASH_LENGTH];
+        //System.arraycopy(val, i + 1, hashData, 0, Hash.HASH_LENGTH);
+        //Hash hash = new Hash(hashData);
+        Hash hash = Hash.create(val, i + 1);
         byte rv[] = new byte[payloadLen];
         System.arraycopy(val, i + 1 + Hash.HASH_LENGTH, rv, 0, rv.length);
 
diff --git a/core/java/src/net/i2p/crypto/SHA256Generator.java b/core/java/src/net/i2p/crypto/SHA256Generator.java
index aa4d041f004ab9d2aa4983db3ecab84243e1a780..8d5b6957b5e2743e83e29429c732236aa475bb23 100644
--- a/core/java/src/net/i2p/crypto/SHA256Generator.java
+++ b/core/java/src/net/i2p/crypto/SHA256Generator.java
@@ -35,7 +35,8 @@ public final class SHA256Generator {
         digest.update(source, start, len);
         byte rv[] = digest.digest();
         releaseGnu(digest);
-        return new Hash(rv);
+        //return new Hash(rv);
+        return Hash.create(rv);
     }
     
     public final void calculateHash(byte[] source, int start, int len, byte out[], int outOffset) {
diff --git a/core/java/src/net/i2p/data/Hash.java b/core/java/src/net/i2p/data/Hash.java
index 96c07c9978eed7d335bb1e8816f63a99d1630057..7512f52f48390fad99b667f65399bda8e5fd9ebf 100644
--- a/core/java/src/net/i2p/data/Hash.java
+++ b/core/java/src/net/i2p/data/Hash.java
@@ -25,7 +25,34 @@ public class Hash extends SimpleDataStructure {
 
     public final static int HASH_LENGTH = 32;
     public final static Hash FAKE_HASH = new Hash(new byte[HASH_LENGTH]);
+    private static final int CACHE_SIZE = 2048;
     
+    private static final SDSCache<Hash> _cache = new SDSCache(Hash.class, HASH_LENGTH, CACHE_SIZE);
+
+    /**
+     * Pull from cache or return new
+     * @since 0.8.3
+     */
+    public static Hash create(byte[] data) {
+        return _cache.get(data);
+    }
+
+    /**
+     * Pull from cache or return new
+     * @since 0.8.3
+     */
+    public static Hash create(byte[] data, int off) {
+        return _cache.get(data, off);
+    }
+
+    /**
+     * Pull from cache or return new
+     * @since 0.8.3
+     */
+    public static Hash create(InputStream in) throws IOException {
+        return _cache.get(in);
+    }
+
     public Hash() {
         super();
     }
diff --git a/core/java/src/net/i2p/data/KeysAndCert.java b/core/java/src/net/i2p/data/KeysAndCert.java
index d27d4bcf455ca97f3b1f8178673da5bb48062acc..ef772880b97c72ad3fc4fe189fe859d212569fbd 100644
--- a/core/java/src/net/i2p/data/KeysAndCert.java
+++ b/core/java/src/net/i2p/data/KeysAndCert.java
@@ -61,10 +61,12 @@ public class KeysAndCert extends DataStructureImpl {
     }
     
     public void readBytes(InputStream in) throws DataFormatException, IOException {
-        _publicKey = new PublicKey();
-        _publicKey.readBytes(in);
-        _signingKey = new SigningPublicKey();
-        _signingKey.readBytes(in);
+        //_publicKey = new PublicKey();
+        //_publicKey.readBytes(in);
+        _publicKey = PublicKey.create(in);
+        //_signingKey = new SigningPublicKey();
+        //_signingKey.readBytes(in);
+        _signingKey = SigningPublicKey.create(in);
         //_certificate = new Certificate();
         //_certificate.readBytes(in);
         _certificate = Certificate.create(in);
diff --git a/core/java/src/net/i2p/data/Lease.java b/core/java/src/net/i2p/data/Lease.java
index 17abdc92b1898c26c9127357d4772c91359ff7a9..283e2aa7c232e3462a4a4dbd170f5d9ce692030b 100644
--- a/core/java/src/net/i2p/data/Lease.java
+++ b/core/java/src/net/i2p/data/Lease.java
@@ -110,8 +110,9 @@ public class Lease extends DataStructureImpl {
     }
     
     public void readBytes(InputStream in) throws DataFormatException, IOException {
-        _gateway = new Hash();
-        _gateway.readBytes(in);
+        //_gateway = new Hash();
+        //_gateway.readBytes(in);
+        _gateway = Hash.create(in);
         _tunnelId = new TunnelId();
         _tunnelId.readBytes(in);
         _end = DataHelper.readDate(in);
diff --git a/core/java/src/net/i2p/data/PublicKey.java b/core/java/src/net/i2p/data/PublicKey.java
index cf6b014346e94ce761299de76b78ea233fc33b97..13b0217f5f371826f3c58058e9c5d3ac3587e293 100644
--- a/core/java/src/net/i2p/data/PublicKey.java
+++ b/core/java/src/net/i2p/data/PublicKey.java
@@ -9,6 +9,9 @@ package net.i2p.data;
  *
  */
 
+import java.io.InputStream;
+import java.io.IOException;
+
 /**
  * Defines the PublicKey as defined by the I2P data structure spec.
  * A public key is 256byte Integer. The public key represents only the 
@@ -18,6 +21,17 @@ package net.i2p.data;
  */
 public class PublicKey extends SimpleDataStructure {
     public final static int KEYSIZE_BYTES = 256;
+    private static final int CACHE_SIZE = 256;
+
+    private static final SDSCache<PublicKey> _cache = new SDSCache(PublicKey.class, KEYSIZE_BYTES, CACHE_SIZE);
+
+    /**
+     * Pull from cache or return new
+     * @since 0.8.3
+     */
+    public static PublicKey create(InputStream in) throws IOException {
+        return _cache.get(in);
+    }
 
     public PublicKey() {
         super();
diff --git a/core/java/src/net/i2p/data/SDSCache.java b/core/java/src/net/i2p/data/SDSCache.java
new file mode 100644
index 0000000000000000000000000000000000000000..562c49c79ae0ca8d880e0022fed1c619f8392d8f
--- /dev/null
+++ b/core/java/src/net/i2p/data/SDSCache.java
@@ -0,0 +1,175 @@
+package net.i2p.data;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import net.i2p.I2PAppContext;
+import net.i2p.util.Log;
+import net.i2p.util.SimpleByteCache;
+
+/**
+ *  A least recently used cache with a max size, for SimpleDataStructures.
+ *  The index to the cache is the first 4 bytes of the data, so
+ *  the data must be sufficiently random.
+ *
+ *  This caches the SDS objects, and also uses SimpleByteCache to cache
+ *  the unused byte arrays themselves
+ *
+ *  Following is sample usage:
+ *  <pre>
+
+    private static final SDSCache<Foo> _cache = new SDSCache(Foo.class, LENGTH, 1024);
+
+    public static Foo create(byte[] data) {
+        return _cache.get(data);
+    }
+
+    public static Foo create(byte[] data, int off) {
+        return _cache.get(data, off);
+    }
+
+    public static Foo create(InputStream in) throws IOException {
+        return _cache.get(in);
+    }
+
+ *  </pre>
+ *  @since 0.8.3
+ *  @author zzz
+ */
+public class SDSCache<V extends SimpleDataStructure> {
+    private static final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(SDSCache.class);
+
+    private static final Class[] conArg = new Class[] { byte[].class };
+    private static final double MIN_FACTOR = 0.25;
+    private static final double MAX_FACTOR = 3.0;
+    private static final double FACTOR;
+    static {
+        long maxMemory = Runtime.getRuntime().maxMemory();
+        FACTOR = Math.max(MIN_FACTOR, Math.min(MAX_FACTOR, maxMemory / (128*1024*1024d)));
+    }
+
+    /** the LRU cache */
+    private final Map<Integer, V> _cache;
+    /** the byte array length for the class we are caching */
+    private final int _datalen;
+    /** the constructor for the class we are caching */
+    private final Constructor<V> _rvCon;
+    private final String _statName;
+
+    /**
+     *  @param rvClass the class that we are storing, i.e. an extension of SimpleDataStructure
+     *  @param len the length of the byte array in the SimpleDataStructure
+     *  @param max maximum size of the cache assuming 128MB of mem.
+     *             The actual max size will be scaled based on available memory.
+     */
+    public SDSCache(Class<V> rvClass, int len, int max) {
+        int size = (int) (max * FACTOR);
+        _cache = new LHM(size);
+        _datalen = len;
+        try {
+            _rvCon = rvClass.getConstructor(conArg);
+        } catch (NoSuchMethodException e) {
+            throw new RuntimeException("SDSCache init error", e);
+        }
+        _statName = "SDSCache." + rvClass.getSimpleName();
+        if (_log.shouldLog(Log.DEBUG))
+            _log.debug("New SDSCache for " + rvClass + " data size: " + len +
+                       " max: " + size + " max mem: " + (len * size));
+        I2PAppContext.getGlobalContext().statManager().createRateStat(_statName, "Hit rate", "Router", new long[] { 10*60*1000 });
+    }
+
+    /**
+     *  @param data non-null, the byte array for the SimpleDataStructure
+     *  @return the cached value if available, otherwise
+     *          makes a new object and returns it
+     *  @throws IllegalArgumentException if data is not the correct number of bytes
+     *  @throws NPE
+     */
+    public V get(byte[] data) {
+        if (data == null)
+            throw new NullPointerException("Don't pull null data from the cache");
+        int found;
+        V rv;
+        Integer key = hashCodeOf(data);
+        synchronized(_cache) {
+            rv = _cache.get(key);
+            if (rv != null && DataHelper.eq(data, rv.getData())) {
+                // found it, we don't need the data passed in any more
+                SimpleByteCache.release(data);
+                found = 1;
+            } else {
+                // make a new one
+                try {
+                    rv = _rvCon.newInstance(new Object[] { data } );
+                } catch (InstantiationException e) {
+                    throw new RuntimeException("SDSCache error", e);
+                } catch (IllegalAccessException e) {
+                    throw new RuntimeException("SDSCache error", e);
+                } catch (InvocationTargetException e) {
+                    throw new RuntimeException("SDSCache error", e);
+                }
+                _cache.put(key, rv);
+               found = 0;
+            }
+        }
+        I2PAppContext.getGlobalContext().statManager().addRateData(_statName, found, 0);
+        return rv;
+    }
+
+    /*
+     *  @param b non-null byte array containing the data, data will be copied to not hold the reference
+     *  @param off offset in the array to start reading from
+     *  @return the cached value if available, otherwise
+     *          makes a new object and returns it
+     *  @throws AIOOBE if not enough bytes
+     *  @throws NPE
+     */
+    public V get(byte[] b, int off) {
+        byte[] data = SimpleByteCache.acquire(_datalen);
+        System.arraycopy(b, off, data, 0, _datalen);
+        return get(data);
+    }
+
+    /*
+     *  @param in a stream from which the bytes will be read
+     *  @return the cached value if available, otherwise
+     *          makes a new object and returns it
+     *  @throws IOException if not enough bytes
+     */
+    public V get(InputStream in) throws IOException {
+        byte[] data = SimpleByteCache.acquire(_datalen);
+        int read = DataHelper.read(in, data);
+        if (read != _datalen)
+            throw new EOFException("Not enough bytes to read the data");
+        return get(data);
+    }
+
+    /**
+     * We assume the data has enough randomness in it, so use the first 4 bytes for speed.
+     */
+    private static Integer hashCodeOf(byte[] data) {
+        int rv = data[0];
+        for (int i = 1; i < 4; i++)
+            rv ^= (data[i] << (i*8));
+        return Integer.valueOf(rv);
+    }
+
+    private static class LHM<K, V> extends LinkedHashMap<K, V> {
+        private final int _max;
+
+        public LHM(int max) {
+            super(max, 0.75f, true);
+            _max = max;
+        }
+
+        @Override
+        protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
+            return size() > _max;
+        }
+    }
+}
diff --git a/core/java/src/net/i2p/data/SigningPublicKey.java b/core/java/src/net/i2p/data/SigningPublicKey.java
index 0e11ad081d159745443f03c9b0ad82e190b78b60..8187b1c185aa37808e69b79ffbfe6aeaed909e28 100644
--- a/core/java/src/net/i2p/data/SigningPublicKey.java
+++ b/core/java/src/net/i2p/data/SigningPublicKey.java
@@ -9,6 +9,9 @@ package net.i2p.data;
  *
  */
 
+import java.io.InputStream;
+import java.io.IOException;
+
 /**
  * Defines the SigningPublicKey as defined by the I2P data structure spec.
  * A public key is 256byte Integer. The public key represents only the 
@@ -19,6 +22,17 @@ package net.i2p.data;
  */
 public class SigningPublicKey extends SimpleDataStructure {
     public final static int KEYSIZE_BYTES = 128;
+    private static final int CACHE_SIZE = 256;
+
+    private static final SDSCache<SigningPublicKey> _cache = new SDSCache(SigningPublicKey.class, KEYSIZE_BYTES, CACHE_SIZE);
+
+    /**
+     * Pull from cache or return new
+     * @since 0.8.3
+     */
+    public static SigningPublicKey create(InputStream in) throws IOException {
+        return _cache.get(in);
+    }
 
     public SigningPublicKey() {
         super();
diff --git a/core/java/src/net/i2p/data/i2cp/DestLookupMessage.java b/core/java/src/net/i2p/data/i2cp/DestLookupMessage.java
index 6ffea95678f7a49c64731088ef89c6fceae0496b..b635936cd64f842366f242f69a1730a12b559837 100644
--- a/core/java/src/net/i2p/data/i2cp/DestLookupMessage.java
+++ b/core/java/src/net/i2p/data/i2cp/DestLookupMessage.java
@@ -33,13 +33,15 @@ public class DestLookupMessage extends I2CPMessageImpl {
     }
 
     protected void doReadMessage(InputStream in, int size) throws I2CPMessageException, IOException {
-        Hash h = new Hash();
+        //Hash h = new Hash();
         try {
-            h.readBytes(in);
-        } catch (DataFormatException dfe) {
+            //h.readBytes(in);
+            _hash = Hash.create(in);
+        //} catch (DataFormatException dfe) {
+        } catch (IllegalArgumentException dfe) {
             throw new I2CPMessageException("Unable to load the hash", dfe);
         }
-        _hash = h;
+        //_hash = h;
     }
 
     protected byte[] doWriteMessage() throws I2CPMessageException, IOException {
diff --git a/core/java/src/net/i2p/data/i2cp/RequestLeaseSetMessage.java b/core/java/src/net/i2p/data/i2cp/RequestLeaseSetMessage.java
index 32241e833861de22d05ad4fa36b6ba09e98673c8..e459d65da1acdf11f41a4afc294bb235412af1bc 100644
--- a/core/java/src/net/i2p/data/i2cp/RequestLeaseSetMessage.java
+++ b/core/java/src/net/i2p/data/i2cp/RequestLeaseSetMessage.java
@@ -86,8 +86,9 @@ public class RequestLeaseSetMessage extends I2CPMessageImpl {
             int numTunnels = (int) DataHelper.readLong(in, 1);
             _endpoints.clear();
             for (int i = 0; i < numTunnels; i++) {
-                Hash router = new Hash();
-                router.readBytes(in);
+                //Hash router = new Hash();
+                //router.readBytes(in);
+                Hash router = Hash.create(in);
                 TunnelId tunnel = new TunnelId();
                 tunnel.readBytes(in);
                 _endpoints.add(new TunnelEndpoint(router, tunnel));
diff --git a/core/java/src/net/i2p/util/ByteCache.java b/core/java/src/net/i2p/util/ByteCache.java
index a95b33de1f50eeafd314d7e0df3aa2fd8cab7b39..3ef72ca10c3ca55d352223eee1868403269e0730 100644
--- a/core/java/src/net/i2p/util/ByteCache.java
+++ b/core/java/src/net/i2p/util/ByteCache.java
@@ -1,9 +1,9 @@
 package net.i2p.util;
 
 import java.util.Arrays;
-import java.util.HashMap;
 import java.util.Map;
 import java.util.Queue;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.LinkedBlockingQueue;
 
 import net.i2p.I2PAppContext;
@@ -57,7 +57,8 @@ import net.i2p.data.ByteArray;
  */
 public final class ByteCache {
 
-    private static final Map<Integer, ByteCache> _caches = new HashMap(16);
+    private static final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(ByteCache.class);
+    private static final Map<Integer, ByteCache> _caches = new ConcurrentHashMap(16);
 
     /**
      *  max size in bytes of each cache
@@ -74,8 +75,9 @@ public final class ByteCache {
     /**
      * Get a cache responsible for objects of the given size
      *
-     * @param cacheSize how large we want the cache to grow before using on 
-     *                  demand allocation
+     * @param cacheSize how large we want the cache to grow 
+     *                  (number of objects, NOT memory size)
+     *                  before discarding released objects.
      *                  Since 0.7.14, a limit of 1MB / size is enforced
      *                  for the typical 128MB max memory JVM
      * @param size how large should the objects cached be?
@@ -84,12 +86,11 @@ public final class ByteCache {
         if (cacheSize * size > MAX_CACHE)
             cacheSize = MAX_CACHE / size;
         Integer sz = Integer.valueOf(size);
-        ByteCache cache = null;
-        synchronized (_caches) {
-            if (!_caches.containsKey(sz))
-                _caches.put(sz, new ByteCache(cacheSize, size));
-            cache = _caches.get(sz);
-        }
+        ByteCache cache = _caches.get(sz);
+        if (cache == null) {
+            cache = new ByteCache(cacheSize, size);
+            _caches.put(sz, cache);
+;       }
         cache.resize(cacheSize);
         //I2PAppContext.getGlobalContext().logManager().getLog(ByteCache.class).error("ByteCache size: " + size + " max: " + cacheSize, new Exception("from"));
         return cache;
@@ -102,10 +103,9 @@ public final class ByteCache {
     public static void clearAll() {
         for (ByteCache bc : _caches.values())
             bc.clear();
-        I2PAppContext.getGlobalContext().logManager().getLog(ByteCache.class).warn("WARNING: Low memory, clearing byte caches");
+        _log.warn("WARNING: Low memory, clearing byte caches");
     }
 
-    private Log _log;
     /** list of available and available entries */
     private Queue<ByteArray> _available;
     private int _maxCached;
@@ -116,7 +116,7 @@ public final class ByteCache {
     private static final boolean _cache = true;
     
     /** how often do we cleanup the cache */
-    private static final int CLEANUP_FREQUENCY = 30*1000;
+    private static final int CLEANUP_FREQUENCY = 33*1000;
     /** if we haven't exceeded the cache size in 2 minutes, cut our cache in half */
     private static final long EXPIRE_PERIOD = 2*60*1000;
     
@@ -126,9 +126,8 @@ public final class ByteCache {
         _maxCached = maxCachedEntries;
         _entrySize = entrySize;
         _lastOverflow = -1;
-        SimpleScheduler.getInstance().addPeriodicEvent(new Cleanup(), CLEANUP_FREQUENCY);
-        _log = I2PAppContext.getGlobalContext().logManager().getLog(ByteCache.class);
-        I2PAppContext.getGlobalContext().statManager().createRateStat("byteCache.memory." + entrySize, "Memory usage (B)", "Router", new long[] { 60*1000 });
+        SimpleScheduler.getInstance().addPeriodicEvent(new Cleanup(), CLEANUP_FREQUENCY + (entrySize % 7));   //stagger
+        I2PAppContext.getGlobalContext().statManager().createRateStat("byteCache.memory." + entrySize, "Memory usage (B)", "Router", new long[] { 10*60*1000 });
     }
     
     private void resize(int maxCachedEntries) {
diff --git a/core/java/src/net/i2p/util/SimpleByteCache.java b/core/java/src/net/i2p/util/SimpleByteCache.java
new file mode 100644
index 0000000000000000000000000000000000000000..50242e8fd3127e015140bf0de2850c98e21a2dd9
--- /dev/null
+++ b/core/java/src/net/i2p/util/SimpleByteCache.java
@@ -0,0 +1,125 @@
+package net.i2p.util;
+
+import java.util.Map;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import net.i2p.I2PAppContext;
+
+/**
+ * Like ByteCache but works directly with byte arrays, not ByteArrays.
+ * These are designed to be small caches, so there's no cleaner task
+ * like there is in ByteCache. And we don't zero out the arrays here.
+ * Only the static methods are public here.
+ *
+ * @since 0.8.3
+ */
+public final class SimpleByteCache {
+
+    private static final Map<Integer, SimpleByteCache> _caches = new ConcurrentHashMap(8);
+
+    private static final int DEFAULT_SIZE = 16;
+
+    /**
+     * Get a cache responsible for arrays of the given size
+     *
+     * @param size how large should the objects cached be?
+     */
+    public static SimpleByteCache getInstance(int size) {
+        return getInstance(DEFAULT_SIZE, size);
+    }
+
+    /**
+     * Get a cache responsible for objects of the given size
+     *
+     * @param cacheSize how large we want the cache to grow 
+     *                  (number of objects, NOT memory size)
+     *                  before discarding released objects.
+     * @param size how large should the objects cached be?
+     */
+    public static SimpleByteCache getInstance(int cacheSize, int size) {
+        Integer sz = Integer.valueOf(size);
+        SimpleByteCache cache = _caches.get(sz);
+        if (cache == null) {
+            cache = new SimpleByteCache(cacheSize, size);
+            _caches.put(sz, cache);
+        }
+        cache.resize(cacheSize);
+        return cache;
+    }
+
+    /**
+     *  Clear everything (memory pressure)
+     */
+    public static void clearAll() {
+        for (SimpleByteCache bc : _caches.values())
+            bc.clear();
+    }
+
+    /** list of available and available entries */
+    private Queue<byte[]> _available;
+    private int _maxCached;
+    private int _entrySize;
+    
+    private SimpleByteCache(int maxCachedEntries, int entrySize) {
+        _available = new LinkedBlockingQueue(maxCachedEntries);
+        _maxCached = maxCachedEntries;
+        _entrySize = entrySize;
+    }
+    
+    private void resize(int maxCachedEntries) {
+        if (_maxCached >= maxCachedEntries) return;
+        _maxCached = maxCachedEntries;
+        // make a bigger one, move the cached items over
+        Queue<byte[]> newLBQ = new LinkedBlockingQueue(maxCachedEntries);
+        byte[] ba;
+        while ((ba = _available.poll()) != null)
+            newLBQ.offer(ba);
+        _available = newLBQ;
+    }
+    
+    /**
+     * Get the next available array, either from the cache or a brand new one
+     */
+    public static byte[] acquire(int size) {
+        return getInstance(size).acquire();
+    }
+
+    /**
+     * Get the next available array, either from the cache or a brand new one
+     */
+    private byte[] acquire() {
+        byte[] rv = _available.poll();
+        if (rv == null)
+            rv = new byte[_entrySize];
+        return rv;
+    }
+    
+    /**
+     * Put this array back onto the available cache for reuse
+     */
+    public static void release(byte[] entry) {
+        SimpleByteCache cache = _caches.get(entry.length);
+        if (cache != null)
+            cache.releaseIt(entry);
+    }
+
+    /**
+     * Put this array back onto the available cache for reuse
+     */
+    private void releaseIt(byte[] entry) {
+        if (entry == null || entry.length != _entrySize)
+            return;
+        // should be safe without this
+        //Arrays.fill(entry, (byte) 0);
+        _available.offer(entry);
+    }
+    
+    /**
+     *  Clear everything (memory pressure)
+     */
+    private void clear() {
+        _available.clear();
+    }
+}
diff --git a/history.txt b/history.txt
index 81b496b5899765399184c49aca6f66e03dd1383a..63449fd25f3628193244ef3bf869863446c865ff 100644
--- a/history.txt
+++ b/history.txt
@@ -1,3 +1,15 @@
+2010-12-30 zzz
+    * Data Structures:
+      - New SDSCache for SimpleDataStructures
+      - New SimpleByteCache for byte[]
+      - Cache Hash, PublicKey, and SigningPublicKey
+      - Remove global lock in ByteCache
+    * I2CP: Missing piece of parallel naming lookup
+    * i2psnark: Fix buttons on Firefox 4.0b
+    * i2ptunnel:
+      - Use dropdown box to select interface for clients
+      - Warn on index page if required fields not set
+
 2010-12-29 zzz
     * Console: Add 500 error page
     * DSAEngine: Restore variants of methods using a Hash argument,
@@ -42,7 +54,9 @@
       - Add support for specifying the timeout for DestLookups
         (can only be smaller than the router timeout for now)
       - Extend dest lookup router timeout from 10s to 15s
-    * i2psnark: Backport TrackerClient NPE fix
+    * i2psnark:
+      - Backport TrackerClient NPE fix
+      - Fix last piece length calculation for torrents > 2GB (ticket #361)
     * i2ptunnel:
       - Get Log from the logManager instead of instantiating,
         so we may adjust the levels on the fly
diff --git a/router/java/src/net/i2p/data/i2np/BuildRequestRecord.java b/router/java/src/net/i2p/data/i2np/BuildRequestRecord.java
index 667d7fb18477372c529b95b37054cfadcabef810..cb44498aaab8649b7bbff915c870ee78c358596e 100644
--- a/router/java/src/net/i2p/data/i2np/BuildRequestRecord.java
+++ b/router/java/src/net/i2p/data/i2np/BuildRequestRecord.java
@@ -93,9 +93,10 @@ public class BuildRequestRecord {
      * the gateway to which the reply should be sent.
      */
     public Hash readNextIdentity() {
-        byte rv[] = new byte[Hash.HASH_LENGTH];
-        System.arraycopy(_data.getData(), _data.getOffset() + OFF_SEND_IDENT, rv, 0, Hash.HASH_LENGTH);
-        return new Hash(rv);
+        //byte rv[] = new byte[Hash.HASH_LENGTH];
+        //System.arraycopy(_data.getData(), _data.getOffset() + OFF_SEND_IDENT, rv, 0, Hash.HASH_LENGTH);
+        //return new Hash(rv);
+        return Hash.create(_data.getData(), _data.getOffset() + OFF_SEND_IDENT);
     }
     /**
      * Tunnel layer encryption key that the current hop should use
diff --git a/router/java/src/net/i2p/data/i2np/DatabaseLookupMessage.java b/router/java/src/net/i2p/data/i2np/DatabaseLookupMessage.java
index 80aaa889182b5924add0ad029234814e1c0ba259..18bc54b49c53c58f83688fdbe9e08bafec4f6970 100644
--- a/router/java/src/net/i2p/data/i2np/DatabaseLookupMessage.java
+++ b/router/java/src/net/i2p/data/i2np/DatabaseLookupMessage.java
@@ -133,15 +133,17 @@ public class DatabaseLookupMessage extends I2NPMessageImpl {
         if (type != MESSAGE_TYPE) throw new I2NPMessageException("Message type is incorrect for this message");
         int curIndex = offset;
         
-        byte keyData[] = new byte[Hash.HASH_LENGTH];
-        System.arraycopy(data, curIndex, keyData, 0, Hash.HASH_LENGTH);
+        //byte keyData[] = new byte[Hash.HASH_LENGTH];
+        //System.arraycopy(data, curIndex, keyData, 0, Hash.HASH_LENGTH);
+        _key = Hash.create(data, curIndex);
         curIndex += Hash.HASH_LENGTH;
-        _key = new Hash(keyData);
+        //_key = new Hash(keyData);
         
-        byte fromData[] = new byte[Hash.HASH_LENGTH];
-        System.arraycopy(data, curIndex, fromData, 0, Hash.HASH_LENGTH);
+        //byte fromData[] = new byte[Hash.HASH_LENGTH];
+        //System.arraycopy(data, curIndex, fromData, 0, Hash.HASH_LENGTH);
+        _fromHash = Hash.create(data, curIndex);
         curIndex += Hash.HASH_LENGTH;
-        _fromHash = new Hash(fromData);
+        //_fromHash = new Hash(fromData);
         
         boolean tunnelSpecified = false;
         switch (data[curIndex]) {
@@ -168,10 +170,11 @@ public class DatabaseLookupMessage extends I2NPMessageImpl {
             throw new I2NPMessageException("Invalid number of peers - " + numPeers);
         Set<Hash> peers = new HashSet(numPeers);
         for (int i = 0; i < numPeers; i++) {
-            byte peer[] = new byte[Hash.HASH_LENGTH];
-            System.arraycopy(data, curIndex, peer, 0, Hash.HASH_LENGTH);
+            //byte peer[] = new byte[Hash.HASH_LENGTH];
+            //System.arraycopy(data, curIndex, peer, 0, Hash.HASH_LENGTH);
+            Hash p = Hash.create(data, curIndex);
             curIndex += Hash.HASH_LENGTH;
-            peers.add(new Hash(peer));
+            peers.add(p);
         }
         _dontIncludePeers = peers;
     }
diff --git a/router/java/src/net/i2p/data/i2np/DatabaseSearchReplyMessage.java b/router/java/src/net/i2p/data/i2np/DatabaseSearchReplyMessage.java
index 08f5511dfdeb6dc13da4b44fa903ca9b2028ba66..44ee294e6a9e9eb89d85ac916d1057ccf09cbde8 100644
--- a/router/java/src/net/i2p/data/i2np/DatabaseSearchReplyMessage.java
+++ b/router/java/src/net/i2p/data/i2np/DatabaseSearchReplyMessage.java
@@ -55,26 +55,29 @@ public class DatabaseSearchReplyMessage extends I2NPMessageImpl {
         if (type != MESSAGE_TYPE) throw new I2NPMessageException("Message type is incorrect for this message");
         int curIndex = offset;
         
-        byte keyData[] = new byte[Hash.HASH_LENGTH];
-        System.arraycopy(data, curIndex, keyData, 0, Hash.HASH_LENGTH);
+        //byte keyData[] = new byte[Hash.HASH_LENGTH];
+        //System.arraycopy(data, curIndex, keyData, 0, Hash.HASH_LENGTH);
+        _key = Hash.create(data, curIndex);
         curIndex += Hash.HASH_LENGTH;
-        _key = new Hash(keyData);
+        //_key = new Hash(keyData);
         
         int num = (int)DataHelper.fromLong(data, curIndex, 1);
         curIndex++;
         
         _peerHashes.clear();
         for (int i = 0; i < num; i++) {
-            byte peer[] = new byte[Hash.HASH_LENGTH];
-            System.arraycopy(data, curIndex, peer, 0, Hash.HASH_LENGTH);
+            //byte peer[] = new byte[Hash.HASH_LENGTH];
+            //System.arraycopy(data, curIndex, peer, 0, Hash.HASH_LENGTH);
+            Hash p = Hash.create(data, curIndex);
             curIndex += Hash.HASH_LENGTH;
-            addReply(new Hash(peer));
+            addReply(p);
         }
             
-        byte from[] = new byte[Hash.HASH_LENGTH];
-        System.arraycopy(data, curIndex, from, 0, Hash.HASH_LENGTH);
+        //byte from[] = new byte[Hash.HASH_LENGTH];
+        //System.arraycopy(data, curIndex, from, 0, Hash.HASH_LENGTH);
+        _from = Hash.create(data, curIndex);
         curIndex += Hash.HASH_LENGTH;
-        _from = new Hash(from);
+        //_from = new Hash(from);
 
         //_context.statManager().addRateData("netDb.searchReplyMessageReceive", num*32 + 64, 1);
     }
diff --git a/router/java/src/net/i2p/data/i2np/DatabaseStoreMessage.java b/router/java/src/net/i2p/data/i2np/DatabaseStoreMessage.java
index f85ecce23772e15fdd2c6a1cb21d6fe8d080b4bc..0a2ee498b7c163c13292cbe7cda582c0044bfe92 100644
--- a/router/java/src/net/i2p/data/i2np/DatabaseStoreMessage.java
+++ b/router/java/src/net/i2p/data/i2np/DatabaseStoreMessage.java
@@ -113,10 +113,11 @@ public class DatabaseStoreMessage extends I2NPMessageImpl {
         if (type != MESSAGE_TYPE) throw new I2NPMessageException("Message type is incorrect for this message");
         int curIndex = offset;
         
-        byte keyData[] = new byte[Hash.HASH_LENGTH];
-        System.arraycopy(data, curIndex, keyData, 0, Hash.HASH_LENGTH);
+        //byte keyData[] = new byte[Hash.HASH_LENGTH];
+        //System.arraycopy(data, curIndex, keyData, 0, Hash.HASH_LENGTH);
+        _key = Hash.create(data, curIndex);
         curIndex += Hash.HASH_LENGTH;
-        _key = new Hash(keyData);
+        //_key = new Hash(keyData);
         
         _type = (int)DataHelper.fromLong(data, curIndex, 1);
         curIndex++;
@@ -130,10 +131,11 @@ public class DatabaseStoreMessage extends I2NPMessageImpl {
                 _replyTunnel = new TunnelId(tunnel);
             curIndex += 4;
             
-            byte gw[] = new byte[Hash.HASH_LENGTH];
-            System.arraycopy(data, curIndex, gw, 0, Hash.HASH_LENGTH);
+            //byte gw[] = new byte[Hash.HASH_LENGTH];
+            //System.arraycopy(data, curIndex, gw, 0, Hash.HASH_LENGTH);
+            _replyGateway = Hash.create(data, curIndex);
             curIndex += Hash.HASH_LENGTH;
-            _replyGateway = new Hash(gw);
+            //_replyGateway = new Hash(gw);
         } else {
             _replyTunnel = null;
             _replyGateway = null;
diff --git a/router/java/src/net/i2p/data/i2np/DeliveryInstructions.java b/router/java/src/net/i2p/data/i2np/DeliveryInstructions.java
index 6c9ddb154da78e54d4f493cc20b89e616ebae08b..d086521f528c9decb7eb8212c8b824e704de5912 100644
--- a/router/java/src/net/i2p/data/i2np/DeliveryInstructions.java
+++ b/router/java/src/net/i2p/data/i2np/DeliveryInstructions.java
@@ -90,18 +90,21 @@ public class DeliveryInstructions extends DataStructureImpl {
             case FLAG_MODE_LOCAL:
                 break;
             case FLAG_MODE_DESTINATION:
-                Hash destHash = new Hash();
-                destHash.readBytes(in);
+                //Hash destHash = new Hash();
+                //destHash.readBytes(in);
+                Hash destHash = Hash.create(in);
                 setDestination(destHash);
                 break;
             case FLAG_MODE_ROUTER:
-                Hash routerHash = new Hash();
-                routerHash.readBytes(in);
+                //Hash routerHash = new Hash();
+                //routerHash.readBytes(in);
+                Hash routerHash = Hash.create(in);
                 setRouter(routerHash);
                 break;
             case FLAG_MODE_TUNNEL:
-                Hash tunnelRouterHash = new Hash();
-                tunnelRouterHash.readBytes(in);
+                //Hash tunnelRouterHash = new Hash();
+                //tunnelRouterHash.readBytes(in);
+                Hash tunnelRouterHash = Hash.create(in);
                 setRouter(tunnelRouterHash);
                 TunnelId id = new TunnelId();
                 id.readBytes(in);
@@ -140,22 +143,25 @@ public class DeliveryInstructions extends DataStructureImpl {
             case FLAG_MODE_LOCAL:
                 break;
             case FLAG_MODE_DESTINATION:
-                byte destHash[] = new byte[Hash.HASH_LENGTH];
-                System.arraycopy(data, cur, destHash, 0, Hash.HASH_LENGTH);
+                //byte destHash[] = new byte[Hash.HASH_LENGTH];
+                //System.arraycopy(data, cur, destHash, 0, Hash.HASH_LENGTH);
+                Hash dh = Hash.create(data, cur);
                 cur += Hash.HASH_LENGTH;
-                setDestination(new Hash(destHash));
+                setDestination(dh);
                 break;
             case FLAG_MODE_ROUTER:
-                byte routerHash[] = new byte[Hash.HASH_LENGTH];
-                System.arraycopy(data, cur, routerHash, 0, Hash.HASH_LENGTH);
+                //byte routerHash[] = new byte[Hash.HASH_LENGTH];
+                //System.arraycopy(data, cur, routerHash, 0, Hash.HASH_LENGTH);
+                Hash rh = Hash.create(data, cur);
                 cur += Hash.HASH_LENGTH;
-                setRouter(new Hash(routerHash));
+                setRouter(rh);
                 break;
             case FLAG_MODE_TUNNEL:
-                byte tunnelRouterHash[] = new byte[Hash.HASH_LENGTH];
-                System.arraycopy(data, cur, tunnelRouterHash, 0, Hash.HASH_LENGTH);
+                //byte tunnelRouterHash[] = new byte[Hash.HASH_LENGTH];
+                //System.arraycopy(data, cur, tunnelRouterHash, 0, Hash.HASH_LENGTH);
+                Hash trh = Hash.create(data, cur);
                 cur += Hash.HASH_LENGTH;
-                setRouter(new Hash(tunnelRouterHash));
+                setRouter(trh);
                 setTunnelId(new TunnelId(DataHelper.fromLong(data, cur, 4)));
                 cur += 4;
                 break;
diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java
index f6e6df3a5c0d10fbf1efc2f7069c342c2fe16f4e..4e28a2c8d2676ff98ee569a93ae883ef407d9461 100644
--- a/router/java/src/net/i2p/router/RouterVersion.java
+++ b/router/java/src/net/i2p/router/RouterVersion.java
@@ -18,7 +18,7 @@ public class RouterVersion {
     /** deprecated */
     public final static String ID = "Monotone";
     public final static String VERSION = CoreVersion.VERSION;
-    public final static long BUILD = 4;
+    public final static long BUILD = 5;
 
     /** for example "-test" */
     public final static String EXTRA = "";
diff --git a/router/java/src/net/i2p/router/client/LookupDestJob.java b/router/java/src/net/i2p/router/client/LookupDestJob.java
index 68edbcaa06f5a7a85e269121d265f522b1bf62fe..7db546e91a11489fbe951781eb9c9e8451d36a4f 100644
--- a/router/java/src/net/i2p/router/client/LookupDestJob.java
+++ b/router/java/src/net/i2p/router/client/LookupDestJob.java
@@ -28,7 +28,8 @@ class LookupDestJob extends JobImpl {
     public String getName() { return "LeaseSet Lookup for Client"; }
     public void runJob() {
         DoneJob done = new DoneJob(getContext());
-        getContext().netDb().lookupLeaseSet(_hash, done, done, 10*1000);
+        // TODO add support for specifying the timeout in the lookup message
+        getContext().netDb().lookupLeaseSet(_hash, done, done, 15*1000);
     }
 
     private class DoneJob extends JobImpl {
@@ -41,7 +42,7 @@ class LookupDestJob extends JobImpl {
             if (ls != null)
                 returnDest(ls.getDestination());
             else
-                returnDest(null);
+                returnHash(_hash);
         }
     }
 
@@ -51,4 +52,15 @@ class LookupDestJob extends JobImpl {
             _runner.doSend(msg);
         } catch (I2CPMessageException ime) {}
     }
+
+    /**
+     *  Return the failed hash so the client can correlate replies with requests
+     *  @since 0.8.3
+     */
+    private void returnHash(Hash h) {
+        DestReplyMessage msg = new DestReplyMessage(h);
+        try {
+            _runner.doSend(msg);
+        } catch (I2CPMessageException ime) {}
+    }
 }
diff --git a/router/java/src/net/i2p/router/peermanager/ProfilePersistenceHelper.java b/router/java/src/net/i2p/router/peermanager/ProfilePersistenceHelper.java
index 610dfec4f8a26eb01f07a781b483bf180105a43b..e012fab4906c9a19694a6bd84710429f0028a6df 100644
--- a/router/java/src/net/i2p/router/peermanager/ProfilePersistenceHelper.java
+++ b/router/java/src/net/i2p/router/peermanager/ProfilePersistenceHelper.java
@@ -13,6 +13,7 @@ import java.util.Set;
 import java.util.zip.GZIPInputStream;
 import java.util.zip.GZIPOutputStream;
 
+import net.i2p.data.Base64;
 import net.i2p.data.DataFormatException;
 import net.i2p.data.DataHelper;
 import net.i2p.data.Hash;
@@ -290,15 +291,19 @@ class ProfilePersistenceHelper {
                 _log.warn("Error loading properties from " + file.getName(), ioe);
         }
     }
-    
+
     private Hash getHash(String name) {
         String key = name.substring("profile-".length());
         key = key.substring(0, key.length() - ".dat".length());
-        Hash h = new Hash();
+        //Hash h = new Hash();
         try {
-            h.fromBase64(key);
+            //h.fromBase64(key);
+            byte[] b = Base64.decode(key);
+            if (b == null)
+                return null;
+            Hash h = Hash.create(b);
             return h;
-        } catch (DataFormatException dfe) {
+        } catch (Exception dfe) {
             _log.warn("Invalid base64 [" + key + "]", dfe);
             return null;
         }
diff --git a/router/java/src/net/i2p/router/tunnel/FragmentHandler.java b/router/java/src/net/i2p/router/tunnel/FragmentHandler.java
index 18a06f0362dfa1f4ad68bba6e2d054e2a4f1cc66..7948c5387b7ac4917df0d89841dc5afab4014331 100644
--- a/router/java/src/net/i2p/router/tunnel/FragmentHandler.java
+++ b/router/java/src/net/i2p/router/tunnel/FragmentHandler.java
@@ -327,11 +327,12 @@ public class FragmentHandler {
             offset += 4;
         }
         if ( (type == TYPE_ROUTER) || (type == TYPE_TUNNEL) ) {
-            byte h[] = new byte[Hash.HASH_LENGTH];
             if (offset + Hash.HASH_LENGTH >= preprocessed.length)
                 return -1;
-            System.arraycopy(preprocessed, offset, h, 0, Hash.HASH_LENGTH);
-            router = new Hash(h);
+            //byte h[] = new byte[Hash.HASH_LENGTH];
+            //System.arraycopy(preprocessed, offset, h, 0, Hash.HASH_LENGTH);
+            //router = new Hash(h);
+            router = Hash.create(preprocessed, offset);
             offset += Hash.HASH_LENGTH;
         }
         if (fragmented) {