diff --git a/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java b/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java
index c3070913eedfd41601309d730b53ab9e7ec41956..bf869f41294fecff711a287c7e25a120e087030a 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java
@@ -522,6 +522,11 @@ public class PeerCoordinator implements PeerListener
         
         //Only request a piece we've requested before if there's no other choice.
         if (piece == null) {
+            // AND if there are almost no wanted pieces left (real end game).
+            // If we do end game all the time, we generate lots of extra traffic
+            // when the seeder is super-slow and all the peers are "caught up"
+            if (wantedPieces.size() > 4)
+                return -1;  // nothing to request and not in end game
             // let's not all get on the same piece
             Collections.shuffle(requested);
             Iterator it2 = requested.iterator();
diff --git a/apps/i2psnark/java/src/org/klomp/snark/Snark.java b/apps/i2psnark/java/src/org/klomp/snark/Snark.java
index 12c299840e3e17afef7213e228979ad21af7c985..5b7d32d0e1b37efaa9a9dae959dcfb9007b7ce3e 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/Snark.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/Snark.java
@@ -734,7 +734,7 @@ public class Snark
     //if (debug >= INFO && t != null)
     //  t.printStackTrace();
     stopTorrent();
-    throw new RuntimeException("die bart die");
+    throw new RuntimeException(s + (t == null ? "" : ": " + t.getMessage()));
   }
 
   /**
diff --git a/apps/i2psnark/java/src/org/klomp/snark/Storage.java b/apps/i2psnark/java/src/org/klomp/snark/Storage.java
index 63787fe0530e292da24222ba4f3f2e2ace4147ed..3e341cc09d507909870bbdd7348b395eae92c4b7 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/Storage.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/Storage.java
@@ -286,6 +286,40 @@ public class Storage
     return needed == 0;
   }
 
+  /**
+   *  @param file absolute path (non-directory)
+   *  @return number of bytes remaining; -1 if unknown file
+   *  @since 0.7.14
+   */
+  public long remaining(String file) {
+      long bytes = 0;
+      for (int i = 0; i < rafs.length; i++) {
+          File f = RAFfile[i];
+          if (f != null && f.getAbsolutePath().equals(file)) {
+              if (complete())
+                  return 0;
+              int psz = metainfo.getPieceLength(0);
+              long start = bytes;
+              long end = start + lengths[i];
+              int pc = (int) (bytes / psz);
+              long rv = 0;
+              if (!bitfield.get(pc))
+                  rv = psz - (bytes % psz);
+              for (int j = pc + 1; j * psz < end; j++) {
+                  if (!bitfield.get(j)) {
+                      if ((j+1)*psz < end)
+                          rv += psz;
+                      else
+                          rv += end - (j * psz);
+                  }
+              }
+              return rv;
+          }
+          bytes += lengths[i];
+      }
+      return -1;
+  }
+
   /**
    * The BitField that tells which pieces this storage contains.
    * Do not change this since this is the current state of the storage.
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 a5f33094dca01c9f7fa31533eea77a10cdc5b53e..85c0e917c9b4b1ca45e61da068eed1f6d010aecf 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java
@@ -7,6 +7,7 @@ import java.io.IOException;
 import java.io.PrintWriter;
 import java.text.Collator;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Comparator;
 import java.util.Enumeration;
 import java.util.Iterator;
@@ -37,7 +38,10 @@ import org.klomp.snark.SnarkManager;
 import org.klomp.snark.Storage;
 import org.klomp.snark.TrackerClient;
 
+import org.mortbay.http.HttpResponse;
 import org.mortbay.jetty.servlet.Default;
+import org.mortbay.util.Resource;
+import org.mortbay.util.URI;
 
 /**
  *  We extend Default instead of HTTPServlet so we can handle
@@ -105,13 +109,52 @@ public class I2PSnarkServlet extends Default {
         super.destroy();
     }
 
+    /**
+     * Some parts modified from:
+     * <pre>
+      // ========================================================================
+      // $Id: Default.java,v 1.51 2006/10/08 14:13:18 gregwilkins Exp $
+      // Copyright 199-2004 Mort Bay Consulting Pty. Ltd.
+      // ------------------------------------------------------------------------
+      // Licensed under the Apache License, Version 2.0 (the "License");
+      // you may not use this file except in compliance with the License.
+      // You may obtain a copy of the License at 
+      // http://www.apache.org/licenses/LICENSE-2.0
+      // Unless required by applicable law or agreed to in writing, software
+      // distributed under the License is distributed on an "AS IS" BASIS,
+      // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+      // See the License for the specific language governing permissions and
+      // limitations under the License.
+      // ========================================================================
+     * </pre>
+     *
+     */
     @Override
     public void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
         // this is the part after /i2psnark
         String path = req.getServletPath();
         // index.jsp doesn't work, it is grabbed by the war handler before here
         if (!(path == null || path.equals("/") || path.equals("/index.jsp") || path.equals("/index.html"))) {
-            super.service(req, resp);
+            if (path.endsWith("/")) {
+                // bypass the horrid Resource.getListHTML()
+                String pathInfo = req.getPathInfo();
+                String pathInContext = URI.addPaths(path, pathInfo);
+                resp.setCharacterEncoding("UTF-8");
+                resp.setContentType("text/html; charset=UTF-8");
+                Resource resource = getResource(pathInContext);
+                if (resource == null || (!resource.exists()) || !resource.isDirectory()) {
+                    resp.sendError(HttpResponse.__404_Not_Found);
+                } else {
+                    String base = URI.addPaths(req.getRequestURI(), "/");
+                    String listing = getListHTML(resource, base, true);
+                    if (listing != null)
+                        resp.getWriter().write(listing);
+                    else // shouldn't happen
+                        resp.sendError(HttpResponse.__404_Not_Found);
+                }
+            } else {
+                super.service(req, resp);
+            }
             return;
         }
 
@@ -305,7 +348,7 @@ public class I2PSnarkServlet extends Default {
                 }
             } else if (newURL != null) {
                 if (newURL.startsWith("http://")) {
-                    _manager.addMessage(_("Fetching {0}", newURL));
+                    _manager.addMessage(_("Fetching {0}", urlify(newURL)));
                     I2PAppThread fetch = new I2PAppThread(new FetchAndAdd(_manager, newURL), "Fetch and add");
                     fetch.start();
                 } else {
@@ -643,7 +686,7 @@ public class I2PSnarkServlet extends Default {
         out.write(statusString + "</td>\n\t");
         out.write("<td align=\"left\" class=\"snarkTorrentName " + rowClass + "\">");
         
-        if (remaining == 0) {
+        if (remaining == 0 || snark.meta.getFiles() != null) {
             out.write("<a href=\"" + snark.meta.getName());
             if (snark.meta.getFiles() != null)
                 out.write("/");
@@ -655,7 +698,7 @@ public class I2PSnarkServlet extends Default {
             out.write("\">");
         }
         out.write(filename);
-        if (remaining == 0)
+        if (remaining == 0 || snark.meta.getFiles() != null)
             out.write("</a>");
         // temporarily hardcoded for postman* and anonymity, requires bytemonsoon patch for lookup by info_hash
         String announce = snark.meta.getAnnounce();
@@ -689,7 +732,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) + "/" + formatSize(total)); // 18MB/3GB
+            out.write(formatSize(total-remaining) + " / " + formatSize(total)); // 18MB/3GB
         else
             out.write(formatSize(total)); // 3GB
         out.write("</td>\n\t");
@@ -966,14 +1009,14 @@ public class I2PSnarkServlet extends Default {
 */
         out.write("<tr><td>");
         out.write(_("Total uploader limit"));
-        out.write(": <td><input type=\"text\" name=\"upLimit\" value=\""
+        out.write(": <td><input type=\"text\" name=\"upLimit\" class=\"r\" value=\""
                   + _manager.util().getMaxUploaders() + "\" size=\"3\" maxlength=\"3\" > ");
         out.write(_("peers"));
         out.write("<br>\n");
 
         out.write("<tr><td>");
         out.write(_("Up bandwidth limit"));
-        out.write(": <td><input type=\"text\" name=\"upBW\" value=\""
+        out.write(": <td><input type=\"text\" name=\"upBW\" class=\"r\" value=\""
                   + _manager.util().getMaxUpBW() + "\" size=\"3\" maxlength=\"3\" > KBps <i>(");
         out.write(_("Half available bandwidth recommended."));
         out.write(" <a href=\"/config.jsp\" target=\"blank\">");
@@ -1020,7 +1063,7 @@ public class I2PSnarkServlet extends Default {
 
         out.write("<tr><td>");
         out.write(_("I2CP port"));
-        out.write(": <td><input type=\"text\" name=\"i2cpPort\" value=\"" +
+        out.write(": <td><input type=\"text\" name=\"i2cpPort\" class=\"r\" value=\"" +
                   + _manager.util().getI2CPPort() + "\" size=\"5\" maxlength=\"5\" > <br>\n");
 
         StringBuilder opts = new StringBuilder(64);
@@ -1090,18 +1133,24 @@ public class I2PSnarkServlet extends Default {
     }
 
     // rounding makes us look faster :)
-    private String formatSize(long bytes) {
+    private static String formatSize(long bytes) {
         if (bytes < 5*1024)
-            return bytes + "B";
+            return bytes + " B";
         else if (bytes < 5*1024*1024)
-            return ((bytes + 512)/1024) + "KB";
+            return ((bytes + 512)/1024) + " KB";
         else if (bytes < 10*1024*1024*1024l)
-            return ((bytes + 512*1024)/(1024*1024)) + "MB";
+            return ((bytes + 512*1024)/(1024*1024)) + " MB";
         else
-            return ((bytes + 512*1024*1024)/(1024*1024*1024)) + "GB";
+            return ((bytes + 512*1024*1024)/(1024*1024*1024)) + " GB";
     }
     
-    private static final String HEADER = "<link href=\"../themes/console/snark.css\" rel=\"stylesheet\" type=\"text/css\" >";
+    private static String urlify(String s) {
+        StringBuilder buf = new StringBuilder(256);
+        buf.append("<a href=\"").append(s).append("\">").append(s).append("</a>");
+        return buf.toString();
+    }
+
+    private static final String HEADER = "<link href=\"/themes/console/snark.css\" rel=\"stylesheet\" type=\"text/css\" >";
                                        
 
     private static final String TABLE_HEADER = "<table border=\"0\" class=\"snarkTorrents\" width=\"100%\" cellpadding=\"0 10px\">\n" +
@@ -1112,6 +1161,169 @@ public class I2PSnarkServlet extends Default {
     
     private static final String FOOTER = "</div></div></div></center></body></html>";
 
+    /**
+     * Modded heavily from the Jetty version in Resource.java,
+     * pass Resource as 1st param
+     * All the xxxResource constructors are package local so we can't extend them.
+     *
+     * <pre>
+      // ========================================================================
+      // $Id: Resource.java,v 1.32 2009/05/16 01:53:36 gregwilkins Exp $
+      // Copyright 1996-2004 Mort Bay Consulting Pty. Ltd.
+      // ------------------------------------------------------------------------
+      // Licensed under the Apache License, Version 2.0 (the "License");
+      // you may not use this file except in compliance with the License.
+      // You may obtain a copy of the License at 
+      // http://www.apache.org/licenses/LICENSE-2.0
+      // Unless required by applicable law or agreed to in writing, software
+      // distributed under the License is distributed on an "AS IS" BASIS,
+      // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+      // See the License for the specific language governing permissions and
+      // limitations under the License.
+      // ========================================================================
+     * </pre>
+     *
+     * Get the resource list as a HTML directory listing.
+     * @param r The Resource
+     * @param base The base URL
+     * @param parent True if the parent directory should be included
+     * @return String of HTML
+     */
+    private String getListHTML(Resource r, String base, boolean parent)
+        throws IOException
+    {
+        if (!r.isDirectory())
+            return null;
+        
+        String[] ls = r.list();
+        if (ls==null)
+            return null;
+        Arrays.sort(ls, Collator.getInstance());
+        
+        StringBuilder buf=new StringBuilder(4096);
+        buf.append("<HTML><HEAD><TITLE>");
+        String title = URI.decodePath(base);
+        if (title.startsWith("/i2psnark/"))
+            title = title.substring("/i2psnark/".length());
+
+        // Get the snark associated with this directory
+        Snark snark = null;
+        try {
+            String torrentName;
+            int slash = title.indexOf('/');
+            if (slash > 0)
+                torrentName = title.substring(0, slash) + ".torrent";
+            else
+                torrentName = title + ".torrent";
+            File dataDir = _manager.getDataDir();
+            String torrentAbsPath = (new File(dataDir, torrentName)).getCanonicalPath();
+            snark = _manager.getTorrent(torrentAbsPath);
+        } catch (IOException ioe) {}
+        if (title.endsWith("/"))
+            title = title.substring(0, title.length() - 1);
+        title = _("Torrent") + ": " + title;
+        buf.append(title);
+        buf.append("</TITLE>").append(HEADER).append("</HEAD><BODY>\n<div class=\"snarknavbar\">");
+        buf.append(title);
+        
+        if (parent)
+        {
+            buf.append("\n<br><A HREF=\"");
+            // corrupts utf-8
+            //buf.append(URI.encodePath(URI.addPaths(base,"../")));
+            buf.append(URI.addPaths(base,"../"));
+            buf.append("\"><img border=\"0\" src=\"/themes/console/images/outbound.png\"> ")
+               .append(_("Up to higher level directory")).append("</A>\n");
+        }
+        
+        buf.append("</div><div class=\"page\"><div class=\"mainsection\">" +
+                   "<TABLE BORDER=0 class=\"snarkTorrents\" cellpadding=\"5px 10px\">" +
+                   "<thead><tr><th>").append(_("File")).append("</th><th>").append(_("Size"))
+           .append("</th><th>").append(_("Status")).append("</th></tr></thead>");
+        //DateFormat dfmt=DateFormat.getDateTimeInstance(DateFormat.MEDIUM,
+        //                                               DateFormat.MEDIUM);
+        for (int i=0 ; i< ls.length ; i++)
+        {   
+            String encoded=URI.encodePath(ls[i]);
+            // bugfix for I2P - Backport from Jetty 6 (zero file lengths and last-modified times)
+            // http://jira.codehaus.org/browse/JETTY-361?page=com.atlassian.jira.plugin.system.issuetabpanels%3Achangehistory-tabpanel#issue-tabs
+            // See resource.diff attachment
+            //Resource item = addPath(encoded);
+            Resource item = r.addPath(ls[i]);
+            
+            String rowClass = (i % 2 == 0 ? "snarkTorrentEven" : "snarkTorrentOdd");
+            buf.append("<TR class=\"").append(rowClass).append("\"><TD class=\"snarkFileName ")
+               .append(rowClass).append("\">");
+            
+            // Get completeness and status string
+            boolean complete = false;
+            String status = "";
+            long length = item.length();
+            if (item.isDirectory()) {
+                complete = true;
+                status = _("Directory");
+            } else {
+                if (snark == null) {
+                    status = "Snark not found?";
+                } else {
+                    try {
+                        File f = item.getFile();
+                        if (f != null) {
+                            long remaining = snark.storage.remaining(f.getCanonicalPath());
+                            if (remaining == 0) {
+                                complete = true;
+                                status = _("Complete");
+                            } else if (remaining < 0) {
+                                complete = true;
+                                status = _("File not found in torrent?");
+                            } else if (length <= 0) {
+                                complete = true;
+                                status = _("Complete");
+                            } else {
+                                status = (100 - (100 * remaining / length)) + "% " + _("complete") +
+                                         " (" + DataHelper.formatSize(remaining) + " bytes remaining)";
+                            }
+                        } else {
+                            status = "Not a file?";
+                        }
+                    } catch (IOException ioe) {
+                        status = "Not a file? " + ioe;
+                    }
+                }
+            }
+
+            String path=URI.addPaths(base,encoded);
+            if (item.isDirectory() && !path.endsWith("/"))
+                path=URI.addPaths(path,"/");
+            if (complete) {
+                // thumbnail ?
+                String plc = path.toLowerCase();
+                if (plc.endsWith(".jpg") || plc.endsWith(".png") || plc.endsWith(".gif")) {
+                    buf.append("<a href=\"").append(path).append("\"><img alt=\"\" border=\"0\" class=\"thumb\" src=\"")
+                       .append(path).append("\"></a> ");
+                }
+                buf.append("<A HREF=\"");
+                buf.append(path);
+                buf.append("\">");
+            }
+            buf.append(ls[i]);
+            if (complete)
+                buf.append("</a>");
+            buf.append("</TD><TD ALIGN=right class=\"").append(rowClass).append("\">");
+            if (!item.isDirectory())
+                buf.append(DataHelper.formatSize(length)).append(' ').append(_("Bytes"));
+            buf.append("</TD><TD>");
+            //buf.append(dfmt.format(new Date(item.lastModified())));
+            buf.append(status);
+            buf.append("</TD></TR>\n");
+        }
+        buf.append("</TABLE>\n");
+	buf.append("</div></div></BODY></HTML>\n");
+        
+        return buf.toString();
+    }
+
+
 /** inner class, don't bother reindenting */
 private static class FetchAndAdd implements Runnable {
     private SnarkManager _manager;
@@ -1126,7 +1338,7 @@ private static class FetchAndAdd implements Runnable {
         File file = _manager.util().get(_url, false, 3);
         try {
             if ( (file != null) && (file.exists()) && (file.length() > 0) ) {
-                _manager.addMessage(_("Torrent fetched from {0}", _url));
+                _manager.addMessage(_("Torrent fetched from {0}", urlify(_url)));
                 FileInputStream in = null;
                 try {
                     in = new FileInputStream(file);
@@ -1154,12 +1366,12 @@ private static class FetchAndAdd implements Runnable {
                         _manager.addTorrent(canonical);
                     }
                 } catch (IOException ioe) {
-                    _manager.addMessage(_("Torrent at {0} was not valid", _url) + ": " + ioe.getMessage());
+                    _manager.addMessage(_("Torrent at {0} was not valid", urlify(_url)) + ": " + ioe.getMessage());
                 } finally {
                     try { in.close(); } catch (IOException ioe) {}
                 }
             } else {
-                _manager.addMessage(_("Torrent was not retrieved from {0}", _url));
+                _manager.addMessage(_("Torrent was not retrieved from {0}", urlify(_url)));
             }
         } finally {
             if (file != null) file.delete();
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/StatSummarizer.java b/apps/routerconsole/java/src/net/i2p/router/web/StatSummarizer.java
index 15232fbcc88574fec2383f93dabe2c8c3a78a9e5..0bcaaf315ae62fe59acd4dd23bfc9db62871442b 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/StatSummarizer.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/StatSummarizer.java
@@ -149,6 +149,10 @@ public class StatSummarizer implements Runnable {
         return false;
     }
     
+    /**
+     *  This does the two-data bandwidth graph only.
+     *  For all other graphs see SummaryRenderer
+     */
     public boolean renderRatePng(OutputStream out, int width, int height, boolean hideLegend, boolean hideGrid, boolean hideTitle, boolean showEvents, int periodCount, boolean showCredit) throws IOException {
         long end = _context.clock().now() - 60*1000;
         if (width > GraphHelper.MAX_X)
@@ -166,22 +170,22 @@ public class StatSummarizer implements Runnable {
             def.setTimePeriod(start/1000, 0);
             def.setLowerLimit(0d);
             def.setBaseValue(1024);
-            String title = "Bandwidth usage";
+            String title = _("Bandwidth usage");
             if (!hideTitle)
                 def.setTitle(title);
             String sendName = SummaryListener.createName(_context, "bw.sendRate.60000");
             String recvName = SummaryListener.createName(_context, "bw.recvRate.60000");
             def.datasource(sendName, sendName, sendName, "AVERAGE", "MEMORY");
             def.datasource(recvName, recvName, recvName, "AVERAGE", "MEMORY");
-            def.area(sendName, Color.BLUE, "Outbound bytes/sec");
+            def.area(sendName, Color.BLUE, _("Outbound bytes/sec"));
             //def.line(sendName, Color.BLUE, "Outbound bytes/sec", 3);
-            def.line(recvName, Color.RED, "Inbound bytes/sec@r", 3);
+            def.line(recvName, Color.RED, _("Inbound bytes/sec") + "@r", 3);
             //def.area(recvName, Color.RED, "Inbound bytes/sec@r");
             if (!hideLegend) {
-                def.gprint(sendName, "AVERAGE", "out average: @2@sbytes/sec");
-                def.gprint(sendName, "MAX", " max: @2@sbytes/sec@r");
-                def.gprint(recvName, "AVERAGE", "in average:  @2@sbytes/sec");
-                def.gprint(recvName, "MAX", " max: @2@sbytes/sec@r");
+                def.gprint(sendName, "AVERAGE", _("out average") + ": @2@s" + _("bytes/sec"));
+                def.gprint(sendName, "MAX", ' ' + _("max") + ": @2@s" + _("bytes/sec") + "@r");
+                def.gprint(recvName, "AVERAGE", _("in average") + ":  @2@s" + _("bytes/sec"));
+                def.gprint(recvName, "MAX", ' ' + _("max") + ": @2@s" + _("bytes/sec") + "@r");
             }
             if (!showCredit)
                 def.setShowSignature(false);
@@ -248,4 +252,12 @@ public class StatSummarizer implements Runnable {
         }
         return rv;
     }
+
+    /** translate a string */
+    private String _(String s) {
+        // the RRD font doesn't have zh chars, at least on my system
+        if ("zh".equals(Messages.getLanguage(_context)))
+            return s;
+        return Messages.getString(s, _context);
+    }
 }
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/SummaryListener.java b/apps/routerconsole/java/src/net/i2p/router/web/SummaryListener.java
index f34659cdbb4f169f5f2a989ed4b876c42f3f6116..0d066233ed80aabc390968a1a9cc4dcd11181a31 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/SummaryListener.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/SummaryListener.java
@@ -142,127 +142,3 @@ class SummaryListener implements RateSummaryListener {
     @Override
     public int hashCode() { return _rate.hashCode(); }
 }
-
-class SummaryRenderer {
-    private Log _log;
-    private SummaryListener _listener;
-    public SummaryRenderer(I2PAppContext ctx, SummaryListener lsnr) { 
-        _log = ctx.logManager().getLog(SummaryRenderer.class);
-        _listener = lsnr;
-    }
-    
-    /**
-     * Render the stats as determined by the specified JRobin xml config,
-     * but note that this doesn't work on stock jvms, as it requires 
-     * DOM level 3 load and store support.  Perhaps we can bundle that, or
-     * specify who can get it from where, etc.
-     *
-     */
-    public static synchronized void render(I2PAppContext ctx, OutputStream out, String filename) throws IOException {
-        long end = ctx.clock().now() - 60*1000;
-        long start = end - 60*1000*SummaryListener.PERIODS;
-        try {
-            RrdGraphDefTemplate template = new RrdGraphDefTemplate(filename);
-            RrdGraphDef def = template.getRrdGraphDef();
-            def.setTimePeriod(start/1000, end/1000); // ignore the periods in the template
-            RrdGraph graph = new RrdGraph(def);
-            byte img[] = graph.getPNGBytes();
-            out.write(img);
-        } catch (RrdException re) {
-            //_log.error("Error rendering " + filename, re);
-            throw new IOException("Error plotting: " + re.getMessage());
-        } catch (IOException ioe) {
-            //_log.error("Error rendering " + filename, ioe);
-            throw ioe;
-        }
-    }
-    public void render(OutputStream out) throws IOException { render(out, -1, -1, false, false, false, false, -1, false); }
-    public void render(OutputStream out, int width, int height, boolean hideLegend, boolean hideGrid, boolean hideTitle, boolean showEvents, int periodCount, boolean showCredit) throws IOException {
-        long end = _listener.now() - 60*1000;
-        if (periodCount <= 0) periodCount = SummaryListener.PERIODS;
-        if (periodCount > SummaryListener.PERIODS)
-            periodCount = SummaryListener.PERIODS;
-        long start = end - _listener.getRate().getPeriod()*periodCount;
-        //long begin = System.currentTimeMillis();
-        try {
-            RrdGraphDef def = new RrdGraphDef();
-            def.setTimePeriod(start/1000, 0);
-            def.setLowerLimit(0d);
-            String name = _listener.getRate().getRateStat().getName();
-            // heuristic to set K=1024
-            if ((name.startsWith("bw.") || name.indexOf("Size") >= 0 || name.indexOf("Bps") >= 0 || name.indexOf("memory") >= 0)
-                && !showEvents)
-                def.setBaseValue(1024);
-            String title = name;
-            if (showEvents)
-                title = title + " events in ";
-            else
-                title = title + " averaged for ";
-            title = title + DataHelper.formatDuration(_listener.getRate().getPeriod());
-            if (!hideTitle)
-                def.setTitle(title);
-            String path = _listener.getData().getPath();
-            String dsNames[] = _listener.getData().getDsNames();
-            String plotName = null;
-            String descr = null;
-            if (showEvents) {
-                // include the average event count on the plot
-                plotName = dsNames[1];
-                descr = "Events per period";
-            } else {
-                // include the average value
-                plotName = dsNames[0];
-                descr = _listener.getRate().getRateStat().getDescription();
-            }
-            def.datasource(plotName, path, plotName, "AVERAGE", "MEMORY");
-            def.area(plotName, Color.BLUE, descr + "@r");
-            if (!hideLegend) {
-                def.gprint(plotName, "AVERAGE", "avg: @2@s");
-                def.gprint(plotName, "MAX", " max: @2@s");
-                def.gprint(plotName, "LAST", " now: @2@s@r");
-            }
-            if (!showCredit)
-                def.setShowSignature(false);
-            /*
-            // these four lines set up a graph plotting both values and events on the same chart
-            // (but with the same coordinates, so the values may look pretty skewed)
-                def.datasource(dsNames[0], path, dsNames[0], "AVERAGE", "MEMORY");
-                def.datasource(dsNames[1], path, dsNames[1], "AVERAGE", "MEMORY");
-                def.area(dsNames[0], Color.BLUE, _listener.getRate().getRateStat().getDescription());
-                def.line(dsNames[1], Color.RED, "Events per period");
-            */
-            if (hideLegend) 
-                def.setShowLegend(false);
-            if (hideGrid) {
-                def.setGridX(false);
-                def.setGridY(false);
-            }
-            //System.out.println("rendering: path=" + path + " dsNames[0]=" + dsNames[0] + " dsNames[1]=" + dsNames[1] + " lsnr.getName=" + _listener.getName());
-            def.setAntiAliasing(false);
-            //System.out.println("Rendering: \n" + def.exportXmlTemplate());
-            //System.out.println("*****************\nData: \n" + _listener.getData().dump());
-            RrdGraph graph = new RrdGraph(def);
-            //System.out.println("Graph created");
-            byte data[] = null;
-            if ( (width <= 0) || (height <= 0) )
-                data = graph.getPNGBytes();
-            else
-                data = graph.getPNGBytes(width, height);
-            //long timeToPlot = System.currentTimeMillis() - begin;
-            out.write(data);
-            //File t = File.createTempFile("jrobinData", ".xml");
-            //_listener.getData().dumpXml(new FileOutputStream(t));
-            //System.out.println("plotted: " + (data != null ? data.length : 0) + " bytes in " + timeToPlot
-            //                   ); // + ", data written to " + t.getAbsolutePath());
-        } catch (RrdException re) {
-            _log.error("Error rendering", re);
-            throw new IOException("Error plotting: " + re.getMessage());
-        } catch (IOException ioe) {
-            _log.error("Error rendering", ioe);
-            throw ioe;
-        } catch (OutOfMemoryError oom) {
-            _log.error("Error rendering", oom);
-            throw new IOException("Error plotting: " + oom.getMessage());
-        }
-    }
-}
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/SummaryRenderer.java b/apps/routerconsole/java/src/net/i2p/router/web/SummaryRenderer.java
new file mode 100644
index 0000000000000000000000000000000000000000..7ee62510bac4dff2dc28c8db28e2163a6f955a56
--- /dev/null
+++ b/apps/routerconsole/java/src/net/i2p/router/web/SummaryRenderer.java
@@ -0,0 +1,170 @@
+package net.i2p.router.web;
+
+import java.awt.Color;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import net.i2p.I2PAppContext;
+import net.i2p.data.DataHelper;
+import net.i2p.stat.Rate;
+import net.i2p.stat.RateStat;
+import net.i2p.stat.RateSummaryListener;
+import net.i2p.util.Log;
+
+import org.jrobin.core.RrdBackendFactory;
+import org.jrobin.core.RrdDb;
+import org.jrobin.core.RrdDef;
+import org.jrobin.core.RrdException;
+import org.jrobin.core.RrdMemoryBackendFactory;
+import org.jrobin.core.Sample;
+import org.jrobin.graph.RrdGraph;
+import org.jrobin.graph.RrdGraphDef;
+import org.jrobin.graph.RrdGraphDefTemplate;
+
+class SummaryRenderer {
+    private Log _log;
+    private SummaryListener _listener;
+    private I2PAppContext _context;
+
+    public SummaryRenderer(I2PAppContext ctx, SummaryListener lsnr) { 
+        _log = ctx.logManager().getLog(SummaryRenderer.class);
+        _listener = lsnr;
+        _context = ctx;
+    }
+    
+    /**
+     * Render the stats as determined by the specified JRobin xml config,
+     * but note that this doesn't work on stock jvms, as it requires 
+     * DOM level 3 load and store support.  Perhaps we can bundle that, or
+     * specify who can get it from where, etc.
+     *
+     */
+    public static synchronized void render(I2PAppContext ctx, OutputStream out, String filename) throws IOException {
+        long end = ctx.clock().now() - 60*1000;
+        long start = end - 60*1000*SummaryListener.PERIODS;
+        try {
+            RrdGraphDefTemplate template = new RrdGraphDefTemplate(filename);
+            RrdGraphDef def = template.getRrdGraphDef();
+            def.setTimePeriod(start/1000, end/1000); // ignore the periods in the template
+            RrdGraph graph = new RrdGraph(def);
+            byte img[] = graph.getPNGBytes();
+            out.write(img);
+        } catch (RrdException re) {
+            //_log.error("Error rendering " + filename, re);
+            throw new IOException("Error plotting: " + re.getMessage());
+        } catch (IOException ioe) {
+            //_log.error("Error rendering " + filename, ioe);
+            throw ioe;
+        }
+    }
+
+    public void render(OutputStream out) throws IOException { render(out, -1, -1, false, false, false, false, -1, false); }
+
+    public void render(OutputStream out, int width, int height, boolean hideLegend, boolean hideGrid, boolean hideTitle, boolean showEvents, int periodCount, boolean showCredit) throws IOException {
+        long end = _listener.now() - 60*1000;
+        if (periodCount <= 0) periodCount = SummaryListener.PERIODS;
+        if (periodCount > SummaryListener.PERIODS)
+            periodCount = SummaryListener.PERIODS;
+        long start = end - _listener.getRate().getPeriod()*periodCount;
+        //long begin = System.currentTimeMillis();
+        try {
+            RrdGraphDef def = new RrdGraphDef();
+            def.setTimePeriod(start/1000, 0);
+            def.setLowerLimit(0d);
+            String name = _listener.getRate().getRateStat().getName();
+            // heuristic to set K=1024
+            if ((name.startsWith("bw.") || name.indexOf("Size") >= 0 || name.indexOf("Bps") >= 0 || name.indexOf("memory") >= 0)
+                && !showEvents)
+                def.setBaseValue(1024);
+            if (!hideTitle) {
+                String title;
+                String p = DataHelper.formatDuration(_listener.getRate().getPeriod());
+                if (showEvents)
+                    title = name + ' ' + _("events in {0}", p);
+                else
+                    title = name + ' ' + _("averaged for {0}", p);
+                def.setTitle(title);
+            }
+            String path = _listener.getData().getPath();
+            String dsNames[] = _listener.getData().getDsNames();
+            String plotName = null;
+            String descr = null;
+            if (showEvents) {
+                // include the average event count on the plot
+                plotName = dsNames[1];
+                descr = _("Events per period");
+            } else {
+                // include the average value
+                plotName = dsNames[0];
+                descr = _listener.getRate().getRateStat().getDescription();
+            }
+            def.datasource(plotName, path, plotName, "AVERAGE", "MEMORY");
+            def.area(plotName, Color.BLUE, descr + "@r");
+            if (!hideLegend) {
+                def.gprint(plotName, "AVERAGE", _("avg") + ": @2@s");
+                def.gprint(plotName, "MAX", ' ' + _("max") + ": @2@s");
+                def.gprint(plotName, "LAST", ' ' + _("now") + ": @2@s@r");
+            }
+            if (!showCredit)
+                def.setShowSignature(false);
+            /*
+            // these four lines set up a graph plotting both values and events on the same chart
+            // (but with the same coordinates, so the values may look pretty skewed)
+                def.datasource(dsNames[0], path, dsNames[0], "AVERAGE", "MEMORY");
+                def.datasource(dsNames[1], path, dsNames[1], "AVERAGE", "MEMORY");
+                def.area(dsNames[0], Color.BLUE, _listener.getRate().getRateStat().getDescription());
+                def.line(dsNames[1], Color.RED, "Events per period");
+            */
+            if (hideLegend) 
+                def.setShowLegend(false);
+            if (hideGrid) {
+                def.setGridX(false);
+                def.setGridY(false);
+            }
+            //System.out.println("rendering: path=" + path + " dsNames[0]=" + dsNames[0] + " dsNames[1]=" + dsNames[1] + " lsnr.getName=" + _listener.getName());
+            def.setAntiAliasing(false);
+            //System.out.println("Rendering: \n" + def.exportXmlTemplate());
+            //System.out.println("*****************\nData: \n" + _listener.getData().dump());
+            RrdGraph graph = new RrdGraph(def);
+            //System.out.println("Graph created");
+            byte data[] = null;
+            if ( (width <= 0) || (height <= 0) )
+                data = graph.getPNGBytes();
+            else
+                data = graph.getPNGBytes(width, height);
+            //long timeToPlot = System.currentTimeMillis() - begin;
+            out.write(data);
+            //File t = File.createTempFile("jrobinData", ".xml");
+            //_listener.getData().dumpXml(new FileOutputStream(t));
+            //System.out.println("plotted: " + (data != null ? data.length : 0) + " bytes in " + timeToPlot
+            //                   ); // + ", data written to " + t.getAbsolutePath());
+        } catch (RrdException re) {
+            _log.error("Error rendering", re);
+            throw new IOException("Error plotting: " + re.getMessage());
+        } catch (IOException ioe) {
+            _log.error("Error rendering", ioe);
+            throw ioe;
+        } catch (OutOfMemoryError oom) {
+            _log.error("Error rendering", oom);
+            throw new IOException("Error plotting: " + oom.getMessage());
+        }
+    }
+
+    /** translate a string */
+    private String _(String s) {
+        // the RRD font doesn't have zh chars, at least on my system
+        if ("zh".equals(Messages.getLanguage(_context)))
+            return s;
+        return Messages.getString(s, _context);
+    }
+
+    /**
+     *  translate a string with a parameter
+     */
+    private String _(String s, String o) {
+        // the RRD font doesn't have zh chars, at least on my system
+        if ("zh".equals(Messages.getLanguage(_context)))
+            return s.replace("{0}", o);
+        return Messages.getString(s, o, _context);
+    }
+}
diff --git a/apps/susidns/src/css.css b/apps/susidns/src/css.css
index fae4f94a0bfb11ae7588e01ac1faed6a8d85e630..14ab494b23ba9cd8265b9061b284c2e1a4b64b8f 100644
--- a/apps/susidns/src/css.css
+++ b/apps/susidns/src/css.css
@@ -150,3 +150,27 @@ input[type=submit]:hover {
 
 }
 
+input[type=reset] {
+     border: 1px outset #999;
+     background: #ddf;
+     color: #001;
+     margin: 5px;
+     font: bold 8pt "Lucida Sans Unicode", "Bitstream Vera Sans", Verdana, Tahoma, Helvetica, sans-serif;
+     padding: 1px 2px;
+     text-decoration: none;
+     min-width: 110px;
+     border-radius: 4px;
+     -moz-border-radius: 4px;
+     -khtml-border-radius: 4px;
+     -moz-box-shadow: inset 0px 2px 8px 0px #fff;
+     color: #006;
+     opacity: 0.9;
+}
+
+input[type=reset]:hover {
+     background: #22a;
+     color: #fff;
+     border: 1px solid #f60;
+     opacity: 1.0;
+     -moz-box-shadow: inset 0px 0px 0px 1px #fff;
+}
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 443293886fd1eabf3b474b9c9774b5e89d40ffac..63c4db85acb1f61c285ff7b7a2231ee927d552be 100644
--- a/apps/susidns/src/java/src/i2p/susi/dns/AddressbookBean.java
+++ b/apps/susidns/src/java/src/i2p/susi/dns/AddressbookBean.java
@@ -35,7 +35,9 @@ import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.Properties;
 
+import net.i2p.data.DataFormatException;
 import net.i2p.data.DataHelper;
+import net.i2p.data.Destination;
 
 public class AddressbookBean
 {
@@ -254,15 +256,40 @@ public class AddressbookBean
 				boolean changed = false;
 				int deleted = 0;
 				String name = null;
-				if (action.equals(_("Add"))) {
+				if (action.equals(_("Add")) || action.equals(_("Replace"))) {
 					if( addressbook != null && hostname != null && destination != null ) {
-						addressbook.put( hostname, destination );
-						changed = true;
-						message = _("Destination added.");
-						// clear search when adding
-						search = null;
+						String oldDest = (String) addressbook.get(hostname);
+						if (destination.equals(oldDest)) {
+							message = _("Host name {0} is already in addressbook, unchanged.", hostname);
+						} else if (oldDest != null && !action.equals(_("Replace"))) {
+							message = _("Host name {0} is already in addressbook with a different destination. Click \"Replace\" to overwrite.", hostname);
+						} else {
+							boolean valid = true;
+							try {
+								Destination dest = new Destination(destination);
+							} catch (DataFormatException dfe) {
+								valid = false;
+							}
+							if (valid) {
+								addressbook.put( hostname, destination );
+								changed = true;
+								if (oldDest == null)
+									message = _("Destination added for {0}.", hostname);
+								else
+									message = _("Destination changed for {0}.", hostname);
+								// clear form
+								hostname = null;
+								destination = null;
+							} else {
+								message = _("Invalid Base 64 destination.");
+							}
+						}
+					} else {
+						message = _("Please enter a host name and destination");
 					}
-				} else if (action.equals(_("Delete"))) {
+					// clear search when adding
+					search = null;
+				} else if (action.equals(_("Delete Selected"))) {
 					Iterator it = deletionMarks.iterator();
 					while( it.hasNext() ) {
 						name = (String)it.next();
@@ -340,7 +367,7 @@ public class AddressbookBean
 		return destination;
 	}
 	public void setDestination(String destination) {
-		this.destination = DataHelper.stripHTML(destination);  // XSS
+		this.destination = DataHelper.stripHTML(destination).trim();  // XSS
 	}
 	public String getHostname() {
 		return hostname;
@@ -352,7 +379,7 @@ public class AddressbookBean
 		deletionMarks.addLast( name );
 	}
 	public void setHostname(String hostname) {
-		this.hostname = DataHelper.stripHTML(hostname);  // XSS
+		this.hostname = DataHelper.stripHTML(hostname).trim();  // XSS
 	}
 	private int getBeginInt() {
 		return Math.max(0, Math.min(entries.length - 1, beginIndex));
diff --git a/apps/susidns/src/jsp/addressbook.jsp b/apps/susidns/src/jsp/addressbook.jsp
index ff09b41e023b50048d328729f156c781beaae781..15c76dfc8496054c909c1ff2066b0a08de38583a 100644
--- a/apps/susidns/src/jsp/addressbook.jsp
+++ b/apps/susidns/src/jsp/addressbook.jsp
@@ -160,7 +160,9 @@
 
 <c:if test="${book.master || book.router || book.published || book.private}">
 <div id="buttons">
-<p class="buttons"><input type="submit" name="action" value="<%=intl._("Delete")%>" >
+<p class="buttons">
+<input type="reset" value="<%=intl._("Cancel")%>" >
+<input type="submit" name="action" value="<%=intl._("Delete Selected")%>" >
 </p>
 </div>
 </c:if>
@@ -179,6 +181,7 @@
 <b><%=intl._("Hostname")%>:</b> <input type="text" name="hostname" value="${book.hostname}" size="20">
 <b><%=intl._("Destination")%>:</b> <textarea name="destination" rows="1" style="height: 3em;" cols="40" wrap="off" >${book.destination}</textarea><br/>
 </p><p>
+<input type="submit" name="action" value="<%=intl._("Replace")%>" >
 <input type="submit" name="action" value="<%=intl._("Add")%>" >
 </p>
 </div>
diff --git a/apps/susidns/src/jsp/config.jsp b/apps/susidns/src/jsp/config.jsp
index f8b47ab003950fbd3a914cb41049d4615901572a..4ad8b77dbb73b9bd113ded9e4e726e7640e0775d 100644
--- a/apps/susidns/src/jsp/config.jsp
+++ b/apps/susidns/src/jsp/config.jsp
@@ -69,8 +69,8 @@
 <textarea name="config" rows="10" cols="80">${cfg.config}</textarea>
 </div>
 <div id="buttons">
-<input type="submit" name="action" value="<%=intl._("Save")%>" >
 <input type="submit" name="action" value="<%=intl._("Reload")%>" >
+<input type="submit" name="action" value="<%=intl._("Save")%>" >
 </div>
 </form>
 <div id="help">
diff --git a/apps/susidns/src/jsp/subscriptions.jsp b/apps/susidns/src/jsp/subscriptions.jsp
index 29bad172459f9ed21172bcab51fe6436ad4c19eb..27af719847b3a1167336e9263c7a14f1a82edbba 100644
--- a/apps/susidns/src/jsp/subscriptions.jsp
+++ b/apps/susidns/src/jsp/subscriptions.jsp
@@ -69,8 +69,8 @@
 <textarea name="content" rows="10" cols="80">${subs.content}</textarea>
 </div>
 <div id="buttons">
-<input type="submit" name="action" value="<%=intl._("Save")%>" >
 <input type="submit" name="action" value="<%=intl._("Reload")%>" >
+<input type="submit" name="action" value="<%=intl._("Save")%>" >
 </div>
 </form>
 <div id="help">
diff --git a/core/c/jbigi/build.sh b/core/c/jbigi/build.sh
index 13678a6d5c223a8b490bf49a9b0c0a947e1bf384..535e451f5286c03a1ea2a6c83fb30ae4ee629517 100755
--- a/core/c/jbigi/build.sh
+++ b/core/c/jbigi/build.sh
@@ -54,7 +54,7 @@ cp *jbigi???* ../../lib/
 echo 'Library copied to lib/'
 cd ../..
 
-I2P=~/i2p
+I2P=~i2p
 if [ ! -f $I2P/lib/i2p.jar ]
 then
 	echo "I2P installation not found in $I2P - correct \$I2P definition in script to run speed test"
diff --git a/core/java/src/net/i2p/data/Address.java b/core/java/src/net/i2p/data/Address.java
index fb87f988ea3e54c394956d25122604210a797b92..777be8661221c205f4b6c9939bd7aac4b119cf17 100644
--- a/core/java/src/net/i2p/data/Address.java
+++ b/core/java/src/net/i2p/data/Address.java
@@ -9,8 +9,6 @@ public class Address extends DataStructureImpl {
     private Destination _destination;
     
     public Address() {
-        _hostname = null;
-        _destination = null;
     }
 
     public String getHostname() {
diff --git a/core/java/src/net/i2p/data/ByteArray.java b/core/java/src/net/i2p/data/ByteArray.java
index 1ececc305b11e03555de4a5b23a1991cfb0560b7..7369a27d191c01e63c2d9ca9b4f10807b4f7f344 100644
--- a/core/java/src/net/i2p/data/ByteArray.java
+++ b/core/java/src/net/i2p/data/ByteArray.java
@@ -22,11 +22,9 @@ public class ByteArray implements Serializable, Comparable {
     private int _offset;
 
     public ByteArray() {
-        this(null);
     }
 
     public ByteArray(byte[] data) {
-        _offset = 0;
         _data = data;
         _valid = (data != null ? data.length : 0);
     }
@@ -92,4 +90,4 @@ public class ByteArray implements Serializable, Comparable {
     public final String toBase64() {
         return Base64.encode(_data, _offset, _valid);
     }
-}
\ No newline at end of file
+}
diff --git a/core/java/src/net/i2p/data/Certificate.java b/core/java/src/net/i2p/data/Certificate.java
index 9518baab4374074bf75eb390776bd21e48d4780d..855d474ca7f0f189cd2fd9ab02a74aeebdab0a8a 100644
--- a/core/java/src/net/i2p/data/Certificate.java
+++ b/core/java/src/net/i2p/data/Certificate.java
@@ -42,8 +42,6 @@ public class Certificate extends DataStructureImpl {
     public final static int CERTIFICATE_TYPE_MULTIPLE = 4;
 
     public Certificate() {
-        _type = 0;
-        _payload = null;
     }
 
     public Certificate(int type, byte[] payload) {
diff --git a/core/java/src/net/i2p/data/Destination.java b/core/java/src/net/i2p/data/Destination.java
index 2e01271e6ac596e61b92e547b79dac8775e6ad5d..4a00d78c914ac5aa763a765198f930bdfe44f2ef 100644
--- a/core/java/src/net/i2p/data/Destination.java
+++ b/core/java/src/net/i2p/data/Destination.java
@@ -27,10 +27,6 @@ public class Destination extends DataStructureImpl {
     protected Hash __calculatedHash;
 
     public Destination() {
-        setCertificate(null);
-        setSigningPublicKey(null);
-        setPublicKey(null);
-        __calculatedHash = null;
     }
 
     /**
@@ -38,7 +34,6 @@ public class Destination extends DataStructureImpl {
      * @param s a Base64 representation of the destination, as (eg) is used in hosts.txt
      */
     public Destination(String s) throws DataFormatException {
-        this();
         fromBase64(s);
     }
 
diff --git a/core/java/src/net/i2p/data/Hash.java b/core/java/src/net/i2p/data/Hash.java
index a57f1ef28c86f8388b1589bf01f5db92ce382c2f..83579d11663d2d8f16cb412a079cbab2e24ac115 100644
--- a/core/java/src/net/i2p/data/Hash.java
+++ b/core/java/src/net/i2p/data/Hash.java
@@ -12,13 +12,6 @@ package net.i2p.data;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Set;
-
-import net.i2p.util.Log;
 
 /**
  * Defines the hash as defined by the I2P data structure spec.
@@ -27,20 +20,15 @@ import net.i2p.util.Log;
  * @author jrandom
  */
 public class Hash extends DataStructureImpl {
-    private final static Log _log = new Log(Hash.class);
     private byte[] _data;
     private volatile String _stringified;
     private volatile String _base64ed;
-    private /* FIXME final FIXME */ Map _xorCache;
     private int _cachedHashCode;
 
     public final static int HASH_LENGTH = 32;
     public final static Hash FAKE_HASH = new Hash(new byte[HASH_LENGTH]);
     
-    private static final int MAX_CACHED_XOR = 1024;
-    
     public Hash() {
-        setData(null);
     }
 
     public Hash(byte data[]) {
@@ -58,77 +46,6 @@ public class Hash extends DataStructureImpl {
         _cachedHashCode = calcHashCode();
     }
 
-    /**
-     * Prepare this hash's cache for xor values - very few hashes will need it,
-     * so we don't want to waste the memory, and lazy initialization would incur
-     * online overhead to verify the initialization.
-     *
-     */
-    public void prepareCache() {
-        synchronized (this) {
-            if (_xorCache == null)
-                _xorCache = new HashMap(MAX_CACHED_XOR);
-        }
-    }
-    
-    /**
-     * Calculate the xor with the current object and the specified hash, 
-     * caching values where possible.  Currently this keeps up to MAX_CACHED_XOR
-     * (1024) entries, and uses an essentially random ejection policy.  Later 
-     * perhaps go for an LRU or FIFO?  
-     *
-     * @throws IllegalStateException if you try to use the cache without first 
-     *                               preparing this object's cache via .prepareCache()
-     */
-    public byte[] cachedXor(Hash key) throws IllegalStateException {
-        if (_xorCache == null)
-            throw new IllegalStateException("To use the cache, you must first prepare it");
-        byte[] distance = (byte[])_xorCache.get(key);
-        
-        if (distance == null) {
-            // not cached, lets cache it
-            int cached = 0;
-            synchronized (_xorCache) {
-                int toRemove = _xorCache.size() + 1 - MAX_CACHED_XOR;
-                if (toRemove > 0) {
-                    Set keys = new HashSet(toRemove);
-                    // this removes essentially random keys - we dont maintain any sort
-                    // of LRU or age.  perhaps we should?
-                    int removed = 0;
-                    for (Iterator iter = _xorCache.keySet().iterator(); iter.hasNext() && removed < toRemove; removed++) 
-                        keys.add(iter.next());
-                    for (Iterator iter = keys.iterator(); iter.hasNext(); ) 
-                        _xorCache.remove(iter.next());
-                }
-                distance = DataHelper.xor(key.getData(), getData());
-                _xorCache.put(key, (Object) distance);
-                cached = _xorCache.size();
-            }
-            if (_log.shouldLog(Log.DEBUG)) {
-                // explicit buffer, since the compiler can't guess how long it'll be
-                StringBuilder buf = new StringBuilder(128);
-                buf.append("miss [").append(cached).append("] from ");
-                buf.append(DataHelper.toHexString(getData())).append(" to ");
-                buf.append(DataHelper.toHexString(key.getData()));
-                _log.debug(buf.toString(), new Exception());
-            }
-        } else {
-            if (_log.shouldLog(Log.DEBUG)) {
-                // explicit buffer, since the compiler can't guess how long it'll be
-                StringBuilder buf = new StringBuilder(128);
-                buf.append("hit from ");
-                buf.append(DataHelper.toHexString(getData())).append(" to ");
-                buf.append(DataHelper.toHexString(key.getData()));
-                _log.debug(buf.toString());
-            }
-        }
-        return distance;
-    }
-    
-    public void clearXorCache() {
-        _xorCache = null;
-    }
-    
     public void readBytes(InputStream in) throws DataFormatException, IOException {
         _data = new byte[HASH_LENGTH];
         _stringified = null;
@@ -189,98 +106,4 @@ public class Hash extends DataStructureImpl {
         }
         return _base64ed;
     }
-    
-/********
-    public static void main(String args[]) {
-        testFill();
-        testOverflow();
-        testFillCheck();
-    }
-    
-    private static void testFill() {
-        Hash local = new Hash(new byte[HASH_LENGTH]); // all zeroes
-        local.prepareCache();
-        for (int i = 0; i < MAX_CACHED_XOR; i++) {
-            byte t[] = new byte[HASH_LENGTH];
-            for (int j = 0; j < HASH_LENGTH; j++)
-                t[j] = (byte)((i >> j) & 0xFF);
-            Hash cur = new Hash(t);
-            local.cachedXor(cur);
-            if (local._xorCache.size() != i+1) {
-                _log.error("xor cache size where i=" + i + " isn't correct!  size = " 
-                           + local._xorCache.size());
-                return;
-            }
-        }
-        _log.debug("Fill test passed");
-    }
-    private static void testOverflow() {
-        Hash local = new Hash(new byte[HASH_LENGTH]); // all zeroes
-        local.prepareCache();
-        for (int i = 0; i < MAX_CACHED_XOR*2; i++) {
-            byte t[] = new byte[HASH_LENGTH];
-            for (int j = 0; j < HASH_LENGTH; j++)
-                t[j] = (byte)((i >> j) & 0xFF);
-            Hash cur = new Hash(t);
-            local.cachedXor(cur);
-            if (i < MAX_CACHED_XOR) {
-                if (local._xorCache.size() != i+1) {
-                    _log.error("xor cache size where i=" + i + " isn't correct!  size = " 
-                               + local._xorCache.size());
-                    return;
-                }
-            } else {
-                if (local._xorCache.size() > MAX_CACHED_XOR) {
-                    _log.error("xor cache size where i=" + i + " isn't correct!  size = " 
-                               + local._xorCache.size());
-                    return;
-                }
-            }
-        }
-        _log.debug("overflow test passed");
-    }
-    private static void testFillCheck() {
-        Set hashes = new HashSet();
-        Hash local = new Hash(new byte[HASH_LENGTH]); // all zeroes
-        local.prepareCache();
-        // fill 'er up
-        for (int i = 0; i < MAX_CACHED_XOR; i++) {
-            byte t[] = new byte[HASH_LENGTH];
-            for (int j = 0; j < HASH_LENGTH; j++)
-                t[j] = (byte)((i >> j) & 0xFF);
-            Hash cur = new Hash(t);
-            hashes.add(cur);
-            local.cachedXor(cur);
-            if (local._xorCache.size() != i+1) {
-                _log.error("xor cache size where i=" + i + " isn't correct!  size = " 
-                           + local._xorCache.size());
-                return;
-            }
-        }
-        // now lets recheck using those same hash objects 
-        // and see if they're cached
-        for (Iterator iter = hashes.iterator(); iter.hasNext(); ) {
-            Hash cur = (Hash)iter.next();
-            if (!local._xorCache.containsKey(cur)) {
-                _log.error("checking the cache, we dont have " 
-                           + DataHelper.toHexString(cur.getData()));
-                return;
-            }
-        }
-        // now lets recheck with new objects but the same values 
-        // and see if they'return cached
-        for (int i = 0; i < MAX_CACHED_XOR; i++) {
-            byte t[] = new byte[HASH_LENGTH];
-            for (int j = 0; j < HASH_LENGTH; j++)
-                t[j] = (byte)((i >> j) & 0xFF);
-            Hash cur = new Hash(t);
-            if (!local._xorCache.containsKey(cur)) {
-                _log.error("checking the cache, we do NOT have " 
-                           + DataHelper.toHexString(cur.getData()));
-                return;
-            }
-        }
-        _log.debug("Fill check test passed");
-    }
-*********/
 }
diff --git a/core/java/src/net/i2p/data/Lease.java b/core/java/src/net/i2p/data/Lease.java
index 850763983da2c085a66197398507812ed87f4787..17abdc92b1898c26c9127357d4772c91359ff7a9 100644
--- a/core/java/src/net/i2p/data/Lease.java
+++ b/core/java/src/net/i2p/data/Lease.java
@@ -30,11 +30,6 @@ public class Lease extends DataStructureImpl {
     private int _numFailure;
 
     public Lease() {
-        setGateway(null);
-        setTunnelId(null);
-        setEndDate(null);
-        setNumSuccess(0);
-        setNumFailure(0);
     }
 
     /** Retrieve the router at which the destination can be contacted
diff --git a/core/java/src/net/i2p/data/LeaseSet.java b/core/java/src/net/i2p/data/LeaseSet.java
index 061091c61fa94d5132e5519c6da8a48450070f8d..81de8e32679f7a6beb137a8b25c44cbb0b0e1775 100644
--- a/core/java/src/net/i2p/data/LeaseSet.java
+++ b/core/java/src/net/i2p/data/LeaseSet.java
@@ -78,18 +78,8 @@ public class LeaseSet extends DataStructureImpl {
     public final static int MAX_LEASES = 6;
 
     public LeaseSet() {
-        setDestination(null);
-        setEncryptionKey(null);
-        setSigningKey(null);
-        setSignature(null);
-        setRoutingKey(null);
         _leases = new ArrayList(MAX_LEASES);
-        _routingKeyGenMod = null;
-        _receivedAsPublished = false;
         _firstExpiration = Long.MAX_VALUE;
-        _lastExpiration = 0;
-        _decrypted = false;
-        _checked = false;
     }
 
     public Destination getDestination() {
diff --git a/core/java/src/net/i2p/data/Payload.java b/core/java/src/net/i2p/data/Payload.java
index a16a6441865fd0224d32a558031a51e63c37d457..89cac8ff5dc193bf6c60ffb262c0fdd2bc738f67 100644
--- a/core/java/src/net/i2p/data/Payload.java
+++ b/core/java/src/net/i2p/data/Payload.java
@@ -27,8 +27,6 @@ public class Payload extends DataStructureImpl {
     private byte[] _unencryptedData;
 
     public Payload() {
-        setUnencryptedData(null);
-        setEncryptedData(null);
     }
 
     /**
diff --git a/core/java/src/net/i2p/data/PrivateKey.java b/core/java/src/net/i2p/data/PrivateKey.java
index c4c34b0e896de39c56da5d37768a88eef8930404..48f8ad8164aa89d9e831b05ba9d70bf26eb3cd04 100644
--- a/core/java/src/net/i2p/data/PrivateKey.java
+++ b/core/java/src/net/i2p/data/PrivateKey.java
@@ -28,8 +28,8 @@ public class PrivateKey extends DataStructureImpl {
     public final static int KEYSIZE_BYTES = 256;
 
     public PrivateKey() {
-        setData(null);
     }
+
     public PrivateKey(byte data[]) { setData(data); }
 
     /** constructs from base64
@@ -37,7 +37,6 @@ public class PrivateKey extends DataStructureImpl {
      * on a prior instance of PrivateKey
      */
     public PrivateKey(String base64Data) throws DataFormatException {
-        this();
         fromBase64(base64Data);
     }
 
diff --git a/core/java/src/net/i2p/data/PublicKey.java b/core/java/src/net/i2p/data/PublicKey.java
index 3496f8d55e3234a24676ae8b3ed3c8915e465636..3d822949e231d70a47d02425ef87f4c2d8ec4b6f 100644
--- a/core/java/src/net/i2p/data/PublicKey.java
+++ b/core/java/src/net/i2p/data/PublicKey.java
@@ -26,8 +26,8 @@ public class PublicKey extends DataStructureImpl {
     public final static int KEYSIZE_BYTES = 256;
 
     public PublicKey() {
-        setData(null);
     }
+
     public PublicKey(byte data[]) {
         if ( (data == null) || (data.length != KEYSIZE_BYTES) )
             throw new IllegalArgumentException("Data must be specified, and the correct size");
@@ -39,7 +39,6 @@ public class PublicKey extends DataStructureImpl {
      * on a prior instance of PublicKey
      */
     public PublicKey(String base64Data)  throws DataFormatException {
-        this();
         fromBase64(base64Data);
     }
 
diff --git a/core/java/src/net/i2p/data/RouterAddress.java b/core/java/src/net/i2p/data/RouterAddress.java
index cc582a358e8765437304b6206f5afd60c2a559ad..9d199e57401add70fb9919555d489ee3e78918fc 100644
--- a/core/java/src/net/i2p/data/RouterAddress.java
+++ b/core/java/src/net/i2p/data/RouterAddress.java
@@ -29,9 +29,6 @@ public class RouterAddress extends DataStructureImpl {
 
     public RouterAddress() {
         setCost(-1);
-        setExpiration(null);
-        setTransportStyle(null);
-        setOptions(null);
     }
 
     /**
diff --git a/core/java/src/net/i2p/data/RouterIdentity.java b/core/java/src/net/i2p/data/RouterIdentity.java
index 095b54f449d166a75d5674225383b1c87ba8d04a..310bd11aa6432f28f27c954512076b4771f21d87 100644
--- a/core/java/src/net/i2p/data/RouterIdentity.java
+++ b/core/java/src/net/i2p/data/RouterIdentity.java
@@ -31,10 +31,6 @@ public class RouterIdentity extends DataStructureImpl {
     private Hash __calculatedHash;
 
     public RouterIdentity() {
-        setCertificate(null);
-        setSigningPublicKey(null);
-        setPublicKey(null);
-        __calculatedHash = null;
     }
 
     public Certificate getCertificate() {
diff --git a/core/java/src/net/i2p/data/RouterInfo.java b/core/java/src/net/i2p/data/RouterInfo.java
index 9968a324d7b9156aad76956865e64995d3b63b1f..2d16af82ec84f72ba46855a479e47765f826c55c 100644
--- a/core/java/src/net/i2p/data/RouterInfo.java
+++ b/core/java/src/net/i2p/data/RouterInfo.java
@@ -60,18 +60,9 @@ public class RouterInfo extends DataStructureImpl {
     public static final String BW_CAPABILITY_CHARS = "KLMNO";
     
     public RouterInfo() {
-        setIdentity(null);
-        setPublished(0);
         _addresses = new HashSet(2);
         _peers = new HashSet(0);
         _options = new OrderedProperties();
-        setSignature(null);
-        _validated = false;
-        _isValid = false;
-        _currentRoutingKey = null;
-        _stringified = null;
-        _byteified = null;
-        _hashCodeInitialized = false;
     }
 
     public RouterInfo(RouterInfo old) {
diff --git a/core/java/src/net/i2p/data/SessionKey.java b/core/java/src/net/i2p/data/SessionKey.java
index deaf292b531eb9d43851a05b77aeb8a30b64f45f..a506c8387d8fde3bb1d650ace969209beac0f1e0 100644
--- a/core/java/src/net/i2p/data/SessionKey.java
+++ b/core/java/src/net/i2p/data/SessionKey.java
@@ -27,8 +27,8 @@ public class SessionKey extends DataStructureImpl {
     public static final SessionKey INVALID_KEY = new SessionKey(new byte[KEYSIZE_BYTES]);
 
     public SessionKey() {
-        this(null);
     } 
+
     public SessionKey(byte data[]) {
         setData(data);
     }
diff --git a/core/java/src/net/i2p/data/Signature.java b/core/java/src/net/i2p/data/Signature.java
index 2c35edebc2bc0ea9f335f936ff3232e668d76066..8ed31a5b78da2f1b535e7e4b2abb1769772174dd 100644
--- a/core/java/src/net/i2p/data/Signature.java
+++ b/core/java/src/net/i2p/data/Signature.java
@@ -30,7 +30,8 @@ public class Signature extends DataStructureImpl {
             FAKE_SIGNATURE[i] = 0x00;
     }
 
-    public Signature() { this(null); }
+    public Signature() {}
+
     public Signature(byte data[]) { setData(data); }
 
     public byte[] getData() {
diff --git a/core/java/src/net/i2p/data/SigningPrivateKey.java b/core/java/src/net/i2p/data/SigningPrivateKey.java
index 3c1e71254684062a13b94bb3d165a6dc748d81fb..40fda013aaa9416aaf03baf0b4eb065ea0bc555f 100644
--- a/core/java/src/net/i2p/data/SigningPrivateKey.java
+++ b/core/java/src/net/i2p/data/SigningPrivateKey.java
@@ -28,7 +28,8 @@ public class SigningPrivateKey extends DataStructureImpl {
 
     public final static int KEYSIZE_BYTES = 20;
 
-    public SigningPrivateKey() { this((byte[])null); }
+    public SigningPrivateKey() {}
+
     public SigningPrivateKey(byte data[]) { setData(data); }
 
     /** constructs from base64
@@ -36,7 +37,6 @@ public class SigningPrivateKey extends DataStructureImpl {
      * on a prior instance of SigningPrivateKey
      */
     public SigningPrivateKey(String base64Data)  throws DataFormatException {
-        this();
         fromBase64(base64Data);
     }
 
diff --git a/core/java/src/net/i2p/data/SigningPublicKey.java b/core/java/src/net/i2p/data/SigningPublicKey.java
index 2d2cf2f9b93ea167783c0a7e2986646ff45537d6..4f2b68df8268b432e97a6623cd99da8a538d03db 100644
--- a/core/java/src/net/i2p/data/SigningPublicKey.java
+++ b/core/java/src/net/i2p/data/SigningPublicKey.java
@@ -26,7 +26,8 @@ public class SigningPublicKey extends DataStructureImpl {
 
     public final static int KEYSIZE_BYTES = 128;
 
-    public SigningPublicKey() { this((byte[])null); }
+    public SigningPublicKey() {}
+
     public SigningPublicKey(byte data[]) { setData(data); }
 
     /** constructs from base64
@@ -34,7 +35,6 @@ public class SigningPublicKey extends DataStructureImpl {
      * on a prior instance of SigningPublicKey
      */
     public SigningPublicKey(String base64Data)  throws DataFormatException {
-        this();
         fromBase64(base64Data);
     }
 
diff --git a/core/java/src/net/i2p/data/i2cp/AbuseReason.java b/core/java/src/net/i2p/data/i2cp/AbuseReason.java
index 58c23d5e83b9dbceefd3c1e71f44e74e923e51f1..f631fa591198c67bbb827b9d47b3af7122bd4018 100644
--- a/core/java/src/net/i2p/data/i2cp/AbuseReason.java
+++ b/core/java/src/net/i2p/data/i2cp/AbuseReason.java
@@ -27,7 +27,6 @@ public class AbuseReason extends DataStructureImpl {
     private String _reason;
 
     public AbuseReason() {
-        setReason(null);
     }
 
     public String getReason() {
diff --git a/core/java/src/net/i2p/data/i2cp/CreateLeaseSetMessage.java b/core/java/src/net/i2p/data/i2cp/CreateLeaseSetMessage.java
index cf6759a26b441c6bba0be3e7e46a462e9748cbf8..4425a8d005f9bc6295d34d5e3cc961b4fbcc7f70 100644
--- a/core/java/src/net/i2p/data/i2cp/CreateLeaseSetMessage.java
+++ b/core/java/src/net/i2p/data/i2cp/CreateLeaseSetMessage.java
@@ -33,10 +33,6 @@ public class CreateLeaseSetMessage extends I2CPMessageImpl {
     private PrivateKey _privateKey;
 
     public CreateLeaseSetMessage() {
-        setSessionId(null);
-        setLeaseSet(null);
-        setSigningPrivateKey(null);
-        setPrivateKey(null);
     }
 
     public SessionId getSessionId() {
diff --git a/core/java/src/net/i2p/data/i2cp/DestroySessionMessage.java b/core/java/src/net/i2p/data/i2cp/DestroySessionMessage.java
index 630e0e71860cc0f6dd879ac82204a23f0bcbabab..8b4db852f77cde845f902025f4aa02a825c7c70c 100644
--- a/core/java/src/net/i2p/data/i2cp/DestroySessionMessage.java
+++ b/core/java/src/net/i2p/data/i2cp/DestroySessionMessage.java
@@ -27,7 +27,6 @@ public class DestroySessionMessage extends I2CPMessageImpl {
     private SessionId _sessionId;
 
     public DestroySessionMessage() {
-        setSessionId(null);
     }
 
     public SessionId getSessionId() {
diff --git a/core/java/src/net/i2p/data/i2cp/DisconnectMessage.java b/core/java/src/net/i2p/data/i2cp/DisconnectMessage.java
index f9c32810d720bcd1fd8386436e13de3e95613f7a..1931fead2f28963658aa777459a7f772f3b5e1fe 100644
--- a/core/java/src/net/i2p/data/i2cp/DisconnectMessage.java
+++ b/core/java/src/net/i2p/data/i2cp/DisconnectMessage.java
@@ -27,7 +27,6 @@ public class DisconnectMessage extends I2CPMessageImpl {
     private String _reason;
 
     public DisconnectMessage() {
-        setReason(null);
     }
 
     public String getReason() {
diff --git a/core/java/src/net/i2p/data/i2cp/MessagePayloadMessage.java b/core/java/src/net/i2p/data/i2cp/MessagePayloadMessage.java
index 6c041f11cba41b81e286c69ef7a15cdbb09abcfb..f06fe86f326cf2b2c945f3d2a44c4846020f55a1 100644
--- a/core/java/src/net/i2p/data/i2cp/MessagePayloadMessage.java
+++ b/core/java/src/net/i2p/data/i2cp/MessagePayloadMessage.java
@@ -31,7 +31,6 @@ public class MessagePayloadMessage extends I2CPMessageImpl {
     public MessagePayloadMessage() {
         setSessionId(-1);
         setMessageId(-1);
-        setPayload(null);
     }
 
     public long getSessionId() {
diff --git a/core/java/src/net/i2p/data/i2cp/ReconfigureSessionMessage.java b/core/java/src/net/i2p/data/i2cp/ReconfigureSessionMessage.java
index 5f7314d648cd2f235d5fff309588ca9dc15df7f0..9400091bede49bd3c7fcef052182e009a0f3710a 100644
--- a/core/java/src/net/i2p/data/i2cp/ReconfigureSessionMessage.java
+++ b/core/java/src/net/i2p/data/i2cp/ReconfigureSessionMessage.java
@@ -28,8 +28,6 @@ public class ReconfigureSessionMessage extends I2CPMessageImpl {
     private SessionConfig _sessionConfig;
 
     public ReconfigureSessionMessage() {
-        _sessionId = null;
-        _sessionConfig = null;
     }
 
     public SessionId getSessionId() {
diff --git a/core/java/src/net/i2p/data/i2cp/ReportAbuseMessage.java b/core/java/src/net/i2p/data/i2cp/ReportAbuseMessage.java
index 54b90292e7dffbccbc5b4ff81a2a3c0b598b70fa..137d364c7763e455f54adcf4b60ac3324c76cff1 100644
--- a/core/java/src/net/i2p/data/i2cp/ReportAbuseMessage.java
+++ b/core/java/src/net/i2p/data/i2cp/ReportAbuseMessage.java
@@ -30,10 +30,6 @@ public class ReportAbuseMessage extends I2CPMessageImpl {
     private MessageId _messageId;
 
     public ReportAbuseMessage() {
-        setSessionId(null);
-        setSeverity(null);
-        setReason(null);
-        setMessageId(null);
     }
 
     public SessionId getSessionId() {
diff --git a/core/java/src/net/i2p/data/i2cp/RequestLeaseSetMessage.java b/core/java/src/net/i2p/data/i2cp/RequestLeaseSetMessage.java
index 35249c76cd3c2a1a4c4cd5179d0d1d490e0fe8fe..ef877afe5e2c188fbd2524745ee793071722b566 100644
--- a/core/java/src/net/i2p/data/i2cp/RequestLeaseSetMessage.java
+++ b/core/java/src/net/i2p/data/i2cp/RequestLeaseSetMessage.java
@@ -34,9 +34,7 @@ public class RequestLeaseSetMessage extends I2CPMessageImpl {
     private Date _end;
 
     public RequestLeaseSetMessage() {
-        setSessionId(null);
         _endpoints = new ArrayList();
-        setEndDate(null);
     }
 
     public SessionId getSessionId() {
diff --git a/core/java/src/net/i2p/data/i2cp/SendMessageExpiresMessage.java b/core/java/src/net/i2p/data/i2cp/SendMessageExpiresMessage.java
index 770a6777b4552b20f9554008b43ad68e38a195b9..9bcabe2fb77a4fb875d4bf0d38465a9409314008 100644
--- a/core/java/src/net/i2p/data/i2cp/SendMessageExpiresMessage.java
+++ b/core/java/src/net/i2p/data/i2cp/SendMessageExpiresMessage.java
@@ -34,7 +34,6 @@ public class SendMessageExpiresMessage extends SendMessageMessage {
 
     public SendMessageExpiresMessage() {
         super();
-        setExpiration(null);
     }
 
     public Date getExpiration() {
diff --git a/core/java/src/net/i2p/data/i2cp/SendMessageMessage.java b/core/java/src/net/i2p/data/i2cp/SendMessageMessage.java
index 237306f0dcab756b133b8abbde5e507c42a5c3f7..dc2cef33d03b33d31e7ad5c3796f866102287649 100644
--- a/core/java/src/net/i2p/data/i2cp/SendMessageMessage.java
+++ b/core/java/src/net/i2p/data/i2cp/SendMessageMessage.java
@@ -32,10 +32,6 @@ public class SendMessageMessage extends I2CPMessageImpl {
     private long _nonce;
 
     public SendMessageMessage() {
-        setSessionId(null);
-        setDestination(null);
-        setPayload(null);
-        setNonce(0);
     }
 
     public SessionId getSessionId() {
diff --git a/core/java/src/net/i2p/data/i2cp/SessionConfig.java b/core/java/src/net/i2p/data/i2cp/SessionConfig.java
index 520413620d35ce9a5b483868d4a43b9d0eacfc78..b96918a459d832f544545f929ceb22a95c9bab9f 100644
--- a/core/java/src/net/i2p/data/i2cp/SessionConfig.java
+++ b/core/java/src/net/i2p/data/i2cp/SessionConfig.java
@@ -51,9 +51,7 @@ public class SessionConfig extends DataStructureImpl {
     }
     public SessionConfig(Destination dest) {
         _destination = dest;
-        _signature = null;
         _creationDate = new Date(Clock.getInstance().now());
-        _options = null;
     }
 
     /**
diff --git a/core/java/src/net/i2p/data/i2cp/SessionStatusMessage.java b/core/java/src/net/i2p/data/i2cp/SessionStatusMessage.java
index dab34ed43725da93a344958fab460932aadd6229..b26ec05f0ba48812363fb153bbed01ba832b4318 100644
--- a/core/java/src/net/i2p/data/i2cp/SessionStatusMessage.java
+++ b/core/java/src/net/i2p/data/i2cp/SessionStatusMessage.java
@@ -33,7 +33,6 @@ public class SessionStatusMessage extends I2CPMessageImpl {
     public final static int STATUS_INVALID = 3;
 
     public SessionStatusMessage() {
-        setSessionId(null);
         setStatus(STATUS_INVALID);
     }
 
diff --git a/core/java/src/net/i2p/stat/Frequency.java b/core/java/src/net/i2p/stat/Frequency.java
index 16e4c8f4adfc6d4c10f109a946a483b8e7e96786..6be7dc3c908223df3d9e3079c66864ad53f15def 100644
--- a/core/java/src/net/i2p/stat/Frequency.java
+++ b/core/java/src/net/i2p/stat/Frequency.java
@@ -15,9 +15,6 @@ public class Frequency {
 
     public Frequency(long period) {
         setPeriod(period);
-        setLastEvent(0);
-        setAverageInterval(0);
-        setMinAverageInterval(0);
     }
 
     /** how long is this frequency averaged over? */
diff --git a/core/java/src/net/i2p/stat/Rate.java b/core/java/src/net/i2p/stat/Rate.java
index 764691d6d315ea4937294d0de7f36bc866f11832..25305bce742c51587d41a88f36139327a29d3898 100644
--- a/core/java/src/net/i2p/stat/Rate.java
+++ b/core/java/src/net/i2p/stat/Rate.java
@@ -120,18 +120,6 @@ public class Rate {
      */
     public Rate(long period) throws IllegalArgumentException {
         if (period <= 0) throw new IllegalArgumentException("The period must be strictly positive");
-        _currentTotalValue = 0.0d;
-        _currentEventCount = 0;
-        _currentTotalEventTime = 0;
-        _lastTotalValue = 0.0d;
-        _lastEventCount = 0;
-        _lastTotalEventTime = 0;
-        _extremeTotalValue = 0.0d;
-        _extremeEventCount = 0;
-        _extremeTotalEventTime = 0;
-        _lifetimeTotalValue = 0.0d;
-        _lifetimeEventCount = 0;
-        _lifetimeTotalEventTime = 0;
 
         _creationDate = now();
         _lastCoalesceDate = _creationDate;
diff --git a/core/java/src/net/i2p/util/ByteCache.java b/core/java/src/net/i2p/util/ByteCache.java
index 38df7a03982007fb01449e92255bcc6feeffe78d..d05327744cdb58569dba138e47002002a8686959 100644
--- a/core/java/src/net/i2p/util/ByteCache.java
+++ b/core/java/src/net/i2p/util/ByteCache.java
@@ -15,7 +15,7 @@ import net.i2p.data.ByteArray;
  *
  * Heap size control - survey of usage (April 2010) :
  *
- *  </pre>
+ *  <pre>
 	Size	Max	MaxMem	From
 
 	16	16	256	CryptixAESEngine
diff --git a/core/java/src/net/i2p/util/LogManager.java b/core/java/src/net/i2p/util/LogManager.java
index 20dcd1fd1cbe3e4e2e57edccdcb401c1f0490db3..8bd344d653de3f2052562f2972b62627d765c6e0 100644
--- a/core/java/src/net/i2p/util/LogManager.java
+++ b/core/java/src/net/i2p/util/LogManager.java
@@ -625,4 +625,12 @@ public class LogManager {
             shutdown();
         }
     }
+
+    /**
+     *  Convenience method for LogRecordFormatter
+     *  @since 0.7.14
+     */
+    I2PAppContext getContext() {
+        return _context;
+    }
 }
diff --git a/core/java/src/net/i2p/util/LogRecordFormatter.java b/core/java/src/net/i2p/util/LogRecordFormatter.java
index 72d6ef9ee7d694ce3388127a3a3fb8fc60ed8f92..84a1cb6b8cf7a5acbec2e786d4e687de67525047 100644
--- a/core/java/src/net/i2p/util/LogRecordFormatter.java
+++ b/core/java/src/net/i2p/util/LogRecordFormatter.java
@@ -14,6 +14,8 @@ import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.Date;
 
+import net.i2p.I2PAppContext;
+
 /**
  * Render a log record according to the log manager's settings
  *
@@ -44,7 +46,7 @@ class LogRecordFormatter {
                 buf.append(getThread(rec));
                 break;
             case LogManager.PRIORITY:
-                buf.append(getPriority(rec));
+                buf.append(getPriority(rec, manager.getContext()));
                 break;
             case LogManager.MESSAGE:
                 buf.append(getWhat(rec));
@@ -78,10 +80,23 @@ class LogRecordFormatter {
         return manager.getDateFormat().format(new Date(logRecord.getDate()));
     }
 
+    /** don't translate */
     private static String getPriority(LogRecord rec) {
         return toString(Log.toLevelString(rec.getPriority()), MAX_PRIORITY_LENGTH);
     }
 
+    private static final String BUNDLE_NAME = "net.i2p.router.web.messages";
+
+    /** translate @since 0.7.14 */
+    private static String getPriority(LogRecord rec, I2PAppContext ctx) {
+        int len;
+        if (Translate.getLanguage(ctx).equals("de"))
+            len = 8;  // KRITISCH
+        else
+            len = MAX_PRIORITY_LENGTH;
+        return toString(Translate.getString(Log.toLevelString(rec.getPriority()), ctx, BUNDLE_NAME), len);
+    }
+
     private static String getWhat(LogRecord rec) {
         return rec.getMessage();
     }
@@ -92,6 +107,7 @@ class LogRecordFormatter {
         return toString(src, MAX_WHERE_LENGTH);
     }
 
+    /** truncates or pads to the specified size */
     private static String toString(String str, int size) {
         StringBuilder buf = new StringBuilder();
         if (str == null) str = "";
@@ -101,4 +117,4 @@ class LogRecordFormatter {
             buf.append(' ');
         return buf.toString();
     }
-}
\ No newline at end of file
+}
diff --git a/history.txt b/history.txt
index b728a9f3a7049336eb01b9e984fa66f95052c72f..42ff77f53302fd455fd3e2c9816c1b6f6895fed5 100644
--- a/history.txt
+++ b/history.txt
@@ -1,3 +1,23 @@
+2010-05-21 zzz
+    * i2psnark:
+      - Spiff up dir listings
+      - Urlify some messages
+      - Only go into end game at the end
+
+2010-05-19 zzz
+    * Data: Remove lots of unnecessary initializers
+    * susidns: More validataion when adding entry
+
+2010-05-15 zzz
+    * Console:
+      - Tag text in graphs
+      - Move SummaryRenderer to its own file
+    * Eepsite: Set no-cache in redirecting page
+    * Hash: Move caching XOR methods only used by KBucket
+      into netdb
+    * i2psnark: CSS tweaks
+    * Log: Translate priority
+
 2010-05-13 zzz
     * netdb.jsp debug tweaks
     * Plugins: Try to prevent ZipErrors after upgrade
diff --git a/installer/resources/eepsite.help/index.html b/installer/resources/eepsite.help/index.html
index 295ff07b5cc659668db41bca49eee1a1408d1372..89fc457916d2f1b81c4b4d57bd649214e93ec9e4 100644
--- a/installer/resources/eepsite.help/index.html
+++ b/installer/resources/eepsite.help/index.html
@@ -7,8 +7,10 @@
 #
 -->
 <head>
-<!-- remove the following line to stop redirecting to the help page -->
+<!-- remove the following three lines to stop redirecting to the help page -->
 <meta http-equiv="refresh" content="1;url=/help/" />
+<meta http-equiv="pragma" content="no-cache">
+<meta http-equiv="cache-control" content="no-cache">
 <title>I2P Anonymous Webserver</title>
 </head>
 <body>
diff --git a/installer/resources/themes/console/snark.css b/installer/resources/themes/console/snark.css
index 25872cb55dba346ee0398904c005e1ce2cd5ebe5..70091b13f9fc32745f7d63dd3e56c675d5236e55 100644
--- a/installer/resources/themes/console/snark.css
+++ b/installer/resources/themes/console/snark.css
@@ -108,6 +108,15 @@ td {
      font-size: 8pt;
 }
 
+.snarkFileName {
+     min-width: 25em;
+}
+
+.thumb {
+     max-height: 64px;
+     max-width: 96px;
+}
+
 .snarkNewTorrent {
      font-size: 9pt;
 }
@@ -185,13 +194,33 @@ input {
      font-size: 9pt;
      font-weight: bold;
      text-align: left;
-     padding: 2px;
+     padding: 2px 4px;
+     -moz-border-radius: 4px;
+     border-radius: 4px;
+     border: 1px;
+}
+
+input.r {
+     text-align: right;
 }
 
 select {
      background: #ffe;
      color: #310;
      font: 9pt "Lucida Sans Unicode","Bitstream Vera Sans",Verdana,Tahoma,Helvetica,sans-serif;
+     font-weight: bold;
+     padding: 2px 2px 2px 3px;
+     -moz-border-radius: 4px;
+     border-radius: 4px;
+}
+
+textarea {
+     background: #ffe;
+     color: #310;
+     font-weight: bold;
+     padding: 1px 4px 0px;
+     -moz-border-radius: 4px;
+     border-radius: 4px;
 }
 
 img {
diff --git a/router/java/src/net/i2p/data/i2np/DataMessage.java b/router/java/src/net/i2p/data/i2np/DataMessage.java
index e98630bc8c1a3167cd2d7fc74e8d98a4dac3f36a..dcfff1805bdd52918092841700405a4cfdb3f28d 100644
--- a/router/java/src/net/i2p/data/i2np/DataMessage.java
+++ b/router/java/src/net/i2p/data/i2np/DataMessage.java
@@ -24,7 +24,6 @@ public class DataMessage extends I2NPMessageImpl {
     
     public DataMessage(I2PAppContext context) {
         super(context);
-        _data = null;
     }
     
     public byte[] getData() { 
diff --git a/router/java/src/net/i2p/data/i2np/DatabaseSearchReplyMessage.java b/router/java/src/net/i2p/data/i2np/DatabaseSearchReplyMessage.java
index 085edc5a01c82ba2186504192f145a1bc5ef0493..08f5511dfdeb6dc13da4b44fa903ca9b2028ba66 100644
--- a/router/java/src/net/i2p/data/i2np/DatabaseSearchReplyMessage.java
+++ b/router/java/src/net/i2p/data/i2np/DatabaseSearchReplyMessage.java
@@ -34,9 +34,7 @@ public class DatabaseSearchReplyMessage extends I2NPMessageImpl {
         // do this in netdb if we need it
         //_context.statManager().createRateStat("netDb.searchReplyMessageSend", "How many search reply messages we send", "NetworkDatabase", new long[] { 60*1000, 5*60*1000, 10*60*1000, 60*60*1000 });
         //_context.statManager().createRateStat("netDb.searchReplyMessageReceive", "How many search reply messages we receive", "NetworkDatabase", new long[] { 60*1000, 5*60*1000, 10*60*1000, 60*60*1000 });
-        setSearchKey(null);
         _peerHashes = new ArrayList(3);
-        setFromHash(null);
     }
     
     /**
diff --git a/router/java/src/net/i2p/data/i2np/DatabaseStoreMessage.java b/router/java/src/net/i2p/data/i2np/DatabaseStoreMessage.java
index 1e90ec716105cb887a79341bdeb163654be19f70..f85ecce23772e15fdd2c6a1cb21d6fe8d080b4bc 100644
--- a/router/java/src/net/i2p/data/i2np/DatabaseStoreMessage.java
+++ b/router/java/src/net/i2p/data/i2np/DatabaseStoreMessage.java
@@ -43,12 +43,6 @@ public class DatabaseStoreMessage extends I2NPMessageImpl {
     public DatabaseStoreMessage(I2PAppContext context) {
         super(context);
         setValueType(-1);
-        setKey(null);
-        setLeaseSet(null);
-        setRouterInfo(null);
-        setReplyToken(0);
-        setReplyTunnel(null);
-        setReplyGateway(null);
     }
     
     /**
diff --git a/router/java/src/net/i2p/data/i2np/DeliveryInstructions.java b/router/java/src/net/i2p/data/i2np/DeliveryInstructions.java
index 98935a3947ae3e0d666ce5d1037d4cf38236d118..6c9ddb154da78e54d4f493cc20b89e616ebae08b 100644
--- a/router/java/src/net/i2p/data/i2np/DeliveryInstructions.java
+++ b/router/java/src/net/i2p/data/i2np/DeliveryInstructions.java
@@ -51,14 +51,7 @@ public class DeliveryInstructions extends DataStructureImpl {
     private final static long FLAG_DELAY = 16;
     
     public DeliveryInstructions() {
-        setEncrypted(false);
-        setEncryptionKey(null);
         setDeliveryMode(-1);
-        setDestination(null);
-        setRouter(null);
-        setTunnelId(null);
-        setDelayRequested(false);
-        setDelaySeconds(0);
     }
     
     public boolean getEncrypted() { return _encrypted; }
diff --git a/router/java/src/net/i2p/data/i2np/GarlicClove.java b/router/java/src/net/i2p/data/i2np/GarlicClove.java
index b9bc3d4e766e0112ea2c30d7a165c7e247aed584..1380dfd6044e5a96036b063c6886d34fb5480e13 100644
--- a/router/java/src/net/i2p/data/i2np/GarlicClove.java
+++ b/router/java/src/net/i2p/data/i2np/GarlicClove.java
@@ -40,11 +40,7 @@ public class GarlicClove extends DataStructureImpl {
         _context = context;
         _log = context.logManager().getLog(GarlicClove.class);
         _handler = new I2NPMessageHandler(context);
-        setInstructions(null);
-        setData(null);
         setCloveId(-1);
-        setExpiration(null);
-        setCertificate(null);
     }
     
     public DeliveryInstructions getInstructions() { return _instructions; }
diff --git a/router/java/src/net/i2p/data/i2np/GarlicMessage.java b/router/java/src/net/i2p/data/i2np/GarlicMessage.java
index 72d7e5d06963c0c33cb4fd3326f4d1bf747bebee..cf92ce7589dc95a8320fb510e3d48d17b8c9d050 100644
--- a/router/java/src/net/i2p/data/i2np/GarlicMessage.java
+++ b/router/java/src/net/i2p/data/i2np/GarlicMessage.java
@@ -26,7 +26,6 @@ public class GarlicMessage extends I2NPMessageImpl {
     
     public GarlicMessage(I2PAppContext context) {
         super(context);
-        setData(null);
     }
     
     public byte[] getData() { 
diff --git a/router/java/src/net/i2p/data/i2np/I2NPMessageHandler.java b/router/java/src/net/i2p/data/i2np/I2NPMessageHandler.java
index 814443cb0105a282456f19d3c20cd969a1a13fe2..00493c9b47e9a882b292f301aef37bd81101b0ad 100644
--- a/router/java/src/net/i2p/data/i2np/I2NPMessageHandler.java
+++ b/router/java/src/net/i2p/data/i2np/I2NPMessageHandler.java
@@ -33,7 +33,6 @@ public class I2NPMessageHandler {
     public I2NPMessageHandler(I2PAppContext context) {
         _context = context;
         _log = context.logManager().getLog(I2NPMessageHandler.class);
-        _messageBuffer = null;
         _lastSize = -1;
     }
     
diff --git a/router/java/src/net/i2p/data/i2np/I2NPMessageImpl.java b/router/java/src/net/i2p/data/i2np/I2NPMessageImpl.java
index 6dd63a6abdea5d4b67e679d161b2cf0129c111bf..108ee0eb05d816d114ae7ac1d01bf9eeb854a9ff 100644
--- a/router/java/src/net/i2p/data/i2np/I2NPMessageImpl.java
+++ b/router/java/src/net/i2p/data/i2np/I2NPMessageImpl.java
@@ -54,8 +54,6 @@ public abstract class I2NPMessageImpl extends DataStructureImpl implements I2NPM
         _log = context.logManager().getLog(I2NPMessageImpl.class);
         _expiration = _context.clock().now() + DEFAULT_EXPIRATION_MS;
         _uniqueId = _context.random().nextLong(MAX_ID_VALUE);
-        _written = false;
-        _read = false;
         //_context.statManager().createRateStat("i2np.writeTime", "How long it takes to write an I2NP message", "I2NP", new long[] { 10*60*1000, 60*60*1000 });
         //_context.statManager().createRateStat("i2np.readTime", "How long it takes to read an I2NP message", "I2NP", new long[] { 10*60*1000, 60*60*1000 });
     }
diff --git a/router/java/src/net/i2p/router/JobImpl.java b/router/java/src/net/i2p/router/JobImpl.java
index 38aa860259c4a94ecea3904965e26ab4668a925b..b122d3674afa78cf1d31732544066a1d7579d362 100644
--- a/router/java/src/net/i2p/router/JobImpl.java
+++ b/router/java/src/net/i2p/router/JobImpl.java
@@ -24,8 +24,6 @@ public abstract class JobImpl implements Job {
         _context = context;
         _timing = new JobTiming(context);
         _id = ++_idSrc;
-        _addedBy = null;
-        _madeReadyOn = 0;
     }
     
     public long getJobId() { return _id; }
diff --git a/router/java/src/net/i2p/router/JobStats.java b/router/java/src/net/i2p/router/JobStats.java
index 148fb7410f096a99d7b59f2ef2a245b42c95e25f..19362cff371a623abc7a16067756f965fda0c775 100644
--- a/router/java/src/net/i2p/router/JobStats.java
+++ b/router/java/src/net/i2p/router/JobStats.java
@@ -15,11 +15,8 @@ class JobStats {
     
     public JobStats(String name) {
         _job = name;
-        _numRuns = 0;
-        _totalTime = 0;
         _maxTime = -1;
         _minTime = -1;
-        _totalPendingTime = 0;
         _maxPendingTime = -1;
         _minPendingTime = -1;
     }
diff --git a/router/java/src/net/i2p/router/JobTiming.java b/router/java/src/net/i2p/router/JobTiming.java
index 366587eaa49a4620aeab563d07282d2761f52044..d979074a6968c70f79e2d368caed3c9e565ba492 100644
--- a/router/java/src/net/i2p/router/JobTiming.java
+++ b/router/java/src/net/i2p/router/JobTiming.java
@@ -23,8 +23,6 @@ public class JobTiming implements Clock.ClockUpdateListener {
     public JobTiming(RouterContext context) {
         _context = context;
         _start = context.clock().now();
-        _actualStart = 0;
-        _actualEnd = 0;
         //context.clock().addUpdateListener(this);
     }
     
diff --git a/router/java/src/net/i2p/router/OutNetMessage.java b/router/java/src/net/i2p/router/OutNetMessage.java
index 10aa6ec152961724bfea368b6d7c8d3b7c37fd6e..72f262cc5613084f9a6ecbdd78d057757854f4a1 100644
--- a/router/java/src/net/i2p/router/OutNetMessage.java
+++ b/router/java/src/net/i2p/router/OutNetMessage.java
@@ -66,18 +66,8 @@ public class OutNetMessage {
     public OutNetMessage(RouterContext context) {
         _context = context;
         _log = context.logManager().getLog(OutNetMessage.class);
-        setTarget(null);
-        _message = null;
-        _messageSize = 0;
         setPriority(-1);
         setExpiration(-1);
-        setOnSendJob(null);
-        setOnFailedSendJob(null);
-        setOnReplyJob(null);
-        setOnFailedReplyJob(null);
-        setReplySelector(null);
-        _failedTransports = null;
-        _sendBegin = 0;
         //_createdBy = new Exception("Created by");
         _created = context.clock().now();
         timestamp("Created");
diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java
index c10128fe6223a59f4fa2320e38c3309487438a87..f2522cd4ed540000a936a603d4922f4a287ece1f 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 = 6;
+    public final static long BUILD = 9;
 
     /** for example "-test" */
     public final static String EXTRA = "";
diff --git a/router/java/src/net/i2p/router/TunnelPoolSettings.java b/router/java/src/net/i2p/router/TunnelPoolSettings.java
index ceb3814a976d43a34ce4bcdeae7d8d6098ca86a3..ae894cf14f694e1c960d24bfe3ccb075a874c0ef 100644
--- a/router/java/src/net/i2p/router/TunnelPoolSettings.java
+++ b/router/java/src/net/i2p/router/TunnelPoolSettings.java
@@ -60,12 +60,7 @@ public class TunnelPoolSettings {
         _duration = DEFAULT_DURATION;
         _length = DEFAULT_LENGTH;
         _lengthVariance = DEFAULT_LENGTH_VARIANCE;
-        _lengthOverride = 0;
         _allowZeroHop = DEFAULT_ALLOW_ZERO_HOP;
-        _isInbound = false;
-        _isExploratory = false;
-        _destination = null;
-        _destinationNickname = null;
         _IPRestriction = DEFAULT_IP_RESTRICTION;
         _unknownOptions = new Properties();
         _randomKey = generateRandomKey();
diff --git a/router/java/src/net/i2p/router/TunnelSelectionCriteria.java b/router/java/src/net/i2p/router/TunnelSelectionCriteria.java
deleted file mode 100644
index e06f1976d098c7634c3796affaa73137357604a5..0000000000000000000000000000000000000000
--- a/router/java/src/net/i2p/router/TunnelSelectionCriteria.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package net.i2p.router;
-/*
- * free (adj.): unencumbered; not under the control of others
- * Written by jrandom in 2003 and released into the public domain 
- * with no warranty of any kind, either expressed or implied.  
- * It probably won't make your computer catch on fire, or eat 
- * your children, but it might.  Use at your own risk.
- *
- */
-
-/**
- * Set of criteria for finding a tunnel from the Tunnel Manager
- *
- */
-public class TunnelSelectionCriteria {
-    public final static int MAX_PRIORITY = 100;
-    public final static int MIN_PRIORITY = 0;
-    private int _latencyPriority;
-    private int _anonymityPriority;
-    private int _reliabilityPriority;
-    private int _maxNeeded;
-    private int _minNeeded;
-
-    public TunnelSelectionCriteria() {
-	setLatencyPriority(0);
-	setAnonymityPriority(0);
-	setReliabilityPriority(0);
-	setMinimumTunnelsRequired(0);
-	setMaximumTunnelsRequired(0);
-    }
-    
-    /** priority of the latency for the tunnel */
-    public int getLatencyPriority() { return _latencyPriority; }
-    public void setLatencyPriority(int latencyPriority) { _latencyPriority = latencyPriority; }
-    /** priority of the anonymity for the tunnel */
-    public int getAnonymityPriority() { return _anonymityPriority; }
-    public void setAnonymityPriority(int anonPriority) { _anonymityPriority = anonPriority; }
-    /** priority of the reliability for the tunnel */
-    public int getReliabilityPriority() { return _reliabilityPriority; }
-    public void setReliabilityPriority(int reliabilityPriority) { _reliabilityPriority = reliabilityPriority; }
-    /** max # of tunnels to return */
-    public int getMaximumTunnelsRequired() { return _maxNeeded; }
-    public void setMaximumTunnelsRequired(int maxNeeded) { _maxNeeded = maxNeeded; }
-    /** minimum # of tunnels to return */
-    public int getMinimumTunnelsRequired() { return _minNeeded; }
-    public void setMinimumTunnelsRequired(int minNeeded) { _minNeeded = minNeeded; }
-}
diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/KBucket.java b/router/java/src/net/i2p/router/networkdb/kademlia/KBucket.java
index 5c3c7b0e208f6f0de6bdd555560b1b1754c8dee4..4804f5da536755d593705e363094da745b492949 100644
--- a/router/java/src/net/i2p/router/networkdb/kademlia/KBucket.java
+++ b/router/java/src/net/i2p/router/networkdb/kademlia/KBucket.java
@@ -76,5 +76,5 @@ interface KBucket {
      */
     public Hash generateRandomKey();
     
-    public Hash getLocal();
+    public LocalHash getLocal();
 }
diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/KBucketImpl.java b/router/java/src/net/i2p/router/networkdb/kademlia/KBucketImpl.java
index 2ef143fee97b1f934808618bd873be431589a693..568a283f9b17eabda7f203bf2a03ecdc81cb0b19 100644
--- a/router/java/src/net/i2p/router/networkdb/kademlia/KBucketImpl.java
+++ b/router/java/src/net/i2p/router/networkdb/kademlia/KBucketImpl.java
@@ -39,7 +39,7 @@ class KBucketImpl implements KBucket {
      */
     private final Set<Hash> _entries;
     /** we center the kbucket set on the given hash, and derive distances from this */
-    private Hash _local;
+    private LocalHash _local;
     /** include if any bits equal or higher to this bit (in big endian order) */
     private int _begin;
     /** include if no bits higher than this bit (inclusive) are set */
@@ -49,7 +49,7 @@ class KBucketImpl implements KBucket {
     private static final int SHUFFLE_DELAY = 10*60*1000;
     private I2PAppContext _context;
     
-    public KBucketImpl(I2PAppContext context, Hash local) {
+    public KBucketImpl(I2PAppContext context, LocalHash local) {
         _context = context;
         _log = context.logManager().getLog(KBucketImpl.class);
         _entries = new ConcurrentHashSet(2); //all but the last 1 or 2 buckets will be empty
@@ -57,6 +57,11 @@ class KBucketImpl implements KBucket {
         setLocal(local);
     }
     
+    /** for testing - use above constructor for production to get common caching */
+    public KBucketImpl(I2PAppContext context, Hash local) {
+        this(context, new LocalHash(local));
+    }
+    
     public int getRangeBegin() { return _begin; }
     public int getRangeEnd() { return _end; }
     public void setRange(int lowOrderBitLimit, int highOrderBitLimit) {
@@ -67,8 +72,8 @@ class KBucketImpl implements KBucket {
         return _entries.size();
     }
     
-    public Hash getLocal() { return _local; }
-    private void setLocal(Hash local) {
+    public LocalHash getLocal() { return _local; }
+    private void setLocal(LocalHash local) {
         _local = local; 
         // we want to make sure we've got the cache in place before calling cachedXor
         _local.prepareCache();
@@ -378,7 +383,7 @@ class KBucketImpl implements KBucket {
         int low = 1;
         int high = 3;
         Log log = I2PAppContext.getGlobalContext().logManager().getLog(KBucketImpl.class);
-        Hash local = Hash.FAKE_HASH;
+        LocalHash local = new LocalHash(Hash.FAKE_HASH);
         local.prepareCache();
         KBucketImpl bucket = new KBucketImpl(I2PAppContext.getGlobalContext(), local);
         bucket.setRange(low, high);
@@ -415,7 +420,7 @@ class KBucketImpl implements KBucket {
         int high = 200;
         byte hash[] = new byte[Hash.HASH_LENGTH];
         RandomSource.getInstance().nextBytes(hash);
-        Hash local = new Hash(hash);
+        LocalHash local = new LocalHash(hash);
         local.prepareCache();
         KBucketImpl bucket = new KBucketImpl(I2PAppContext.getGlobalContext(), local);
         bucket.setRange(low, high);
diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/KBucketSet.java b/router/java/src/net/i2p/router/networkdb/kademlia/KBucketSet.java
index 0b8fce229a86aaf2b4cca249bc88058a8f110f25..f576d967c7952b7f79a913fce4ddc0128016be6a 100644
--- a/router/java/src/net/i2p/router/networkdb/kademlia/KBucketSet.java
+++ b/router/java/src/net/i2p/router/networkdb/kademlia/KBucketSet.java
@@ -27,7 +27,7 @@ import net.i2p.util.Log;
 class KBucketSet {
     private Log _log;
     private I2PAppContext _context;
-    private Hash _us;
+    private LocalHash _us;
     private KBucket _buckets[];
     private volatile int _size;
     
@@ -38,7 +38,7 @@ class KBucketSet {
     public final static int BUCKET_SIZE = 500; // # values at which we start periodic trimming (500 ~= 250Kb)
     
     public KBucketSet(I2PAppContext context, Hash us) {
-        _us = us;
+        _us = new LocalHash(us);
         _context = context;
         _log = context.logManager().getLog(KBucketSet.class);
         createBuckets();
diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/LocalHash.java b/router/java/src/net/i2p/router/networkdb/kademlia/LocalHash.java
new file mode 100644
index 0000000000000000000000000000000000000000..890d4300fac27a3c50ee0851eca0b46e99164cd8
--- /dev/null
+++ b/router/java/src/net/i2p/router/networkdb/kademlia/LocalHash.java
@@ -0,0 +1,208 @@
+package net.i2p.router.networkdb.kademlia;
+
+/*
+ * free (adj.): unencumbered; not under the control of others
+ * Written by jrandom in 2003 and released into the public domain 
+ * with no warranty of any kind, either expressed or implied.  
+ * It probably won't make your computer catch on fire, or eat 
+ * your children, but it might.  Use at your own risk.
+ *
+ */
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import net.i2p.data.DataHelper;
+import net.i2p.data.Hash;
+import net.i2p.util.Log;
+
+/**
+ * Pull the caching used only by KBucketImpl out of Hash and put it here.
+ *
+ * @since 0.7.14
+ * @author jrandom
+ * @author moved from Hash.java by zzz
+ */
+class LocalHash extends Hash {
+    private final static Log _log = new Log(LocalHash.class);
+    private /* FIXME final FIXME */ Map _xorCache;
+
+    private static final int MAX_CACHED_XOR = 1024;
+    
+    public LocalHash(Hash h) {
+        super(h.getData());
+    }
+
+    public LocalHash(byte[] b) {
+        super(b);
+    }
+
+    /**
+     * Prepare this hash's cache for xor values - very few hashes will need it,
+     * so we don't want to waste the memory, and lazy initialization would incur
+     * online overhead to verify the initialization.
+     *
+     */
+    public void prepareCache() {
+        synchronized (this) {
+            if (_xorCache == null)
+                _xorCache = new HashMap(MAX_CACHED_XOR);
+        }
+    }
+    
+    /**
+     * Calculate the xor with the current object and the specified hash, 
+     * caching values where possible.  Currently this keeps up to MAX_CACHED_XOR
+     * (1024) entries, and uses an essentially random ejection policy.  Later 
+     * perhaps go for an LRU or FIFO?  
+     *
+     * @throws IllegalStateException if you try to use the cache without first 
+     *                               preparing this object's cache via .prepareCache()
+     */
+    public byte[] cachedXor(Hash key) throws IllegalStateException {
+        if (_xorCache == null)
+            throw new IllegalStateException("To use the cache, you must first prepare it");
+        byte[] distance = (byte[])_xorCache.get(key);
+        
+        if (distance == null) {
+            // not cached, lets cache it
+            int cached = 0;
+            synchronized (_xorCache) {
+                int toRemove = _xorCache.size() + 1 - MAX_CACHED_XOR;
+                if (toRemove > 0) {
+                    Set keys = new HashSet(toRemove);
+                    // this removes essentially random keys - we dont maintain any sort
+                    // of LRU or age.  perhaps we should?
+                    int removed = 0;
+                    for (Iterator iter = _xorCache.keySet().iterator(); iter.hasNext() && removed < toRemove; removed++) 
+                        keys.add(iter.next());
+                    for (Iterator iter = keys.iterator(); iter.hasNext(); ) 
+                        _xorCache.remove(iter.next());
+                }
+                distance = DataHelper.xor(key.getData(), getData());
+                _xorCache.put(key, (Object) distance);
+                cached = _xorCache.size();
+            }
+            if (_log.shouldLog(Log.DEBUG)) {
+                // explicit buffer, since the compiler can't guess how long it'll be
+                StringBuilder buf = new StringBuilder(128);
+                buf.append("miss [").append(cached).append("] from ");
+                buf.append(DataHelper.toHexString(getData())).append(" to ");
+                buf.append(DataHelper.toHexString(key.getData()));
+                _log.debug(buf.toString(), new Exception());
+            }
+        } else {
+            if (_log.shouldLog(Log.DEBUG)) {
+                // explicit buffer, since the compiler can't guess how long it'll be
+                StringBuilder buf = new StringBuilder(128);
+                buf.append("hit from ");
+                buf.append(DataHelper.toHexString(getData())).append(" to ");
+                buf.append(DataHelper.toHexString(key.getData()));
+                _log.debug(buf.toString());
+            }
+        }
+        return distance;
+    }
+    
+    /** @deprecated unused */
+    public void clearXorCache() {
+        _xorCache = null;
+    }
+    
+/********
+    public static void main(String args[]) {
+        testFill();
+        testOverflow();
+        testFillCheck();
+    }
+    
+    private static void testFill() {
+        Hash local = new Hash(new byte[HASH_LENGTH]); // all zeroes
+        local.prepareCache();
+        for (int i = 0; i < MAX_CACHED_XOR; i++) {
+            byte t[] = new byte[HASH_LENGTH];
+            for (int j = 0; j < HASH_LENGTH; j++)
+                t[j] = (byte)((i >> j) & 0xFF);
+            Hash cur = new Hash(t);
+            local.cachedXor(cur);
+            if (local._xorCache.size() != i+1) {
+                _log.error("xor cache size where i=" + i + " isn't correct!  size = " 
+                           + local._xorCache.size());
+                return;
+            }
+        }
+        _log.debug("Fill test passed");
+    }
+    private static void testOverflow() {
+        Hash local = new Hash(new byte[HASH_LENGTH]); // all zeroes
+        local.prepareCache();
+        for (int i = 0; i < MAX_CACHED_XOR*2; i++) {
+            byte t[] = new byte[HASH_LENGTH];
+            for (int j = 0; j < HASH_LENGTH; j++)
+                t[j] = (byte)((i >> j) & 0xFF);
+            Hash cur = new Hash(t);
+            local.cachedXor(cur);
+            if (i < MAX_CACHED_XOR) {
+                if (local._xorCache.size() != i+1) {
+                    _log.error("xor cache size where i=" + i + " isn't correct!  size = " 
+                               + local._xorCache.size());
+                    return;
+                }
+            } else {
+                if (local._xorCache.size() > MAX_CACHED_XOR) {
+                    _log.error("xor cache size where i=" + i + " isn't correct!  size = " 
+                               + local._xorCache.size());
+                    return;
+                }
+            }
+        }
+        _log.debug("overflow test passed");
+    }
+    private static void testFillCheck() {
+        Set hashes = new HashSet();
+        Hash local = new Hash(new byte[HASH_LENGTH]); // all zeroes
+        local.prepareCache();
+        // fill 'er up
+        for (int i = 0; i < MAX_CACHED_XOR; i++) {
+            byte t[] = new byte[HASH_LENGTH];
+            for (int j = 0; j < HASH_LENGTH; j++)
+                t[j] = (byte)((i >> j) & 0xFF);
+            Hash cur = new Hash(t);
+            hashes.add(cur);
+            local.cachedXor(cur);
+            if (local._xorCache.size() != i+1) {
+                _log.error("xor cache size where i=" + i + " isn't correct!  size = " 
+                           + local._xorCache.size());
+                return;
+            }
+        }
+        // now lets recheck using those same hash objects 
+        // and see if they're cached
+        for (Iterator iter = hashes.iterator(); iter.hasNext(); ) {
+            Hash cur = (Hash)iter.next();
+            if (!local._xorCache.containsKey(cur)) {
+                _log.error("checking the cache, we dont have " 
+                           + DataHelper.toHexString(cur.getData()));
+                return;
+            }
+        }
+        // now lets recheck with new objects but the same values 
+        // and see if they'return cached
+        for (int i = 0; i < MAX_CACHED_XOR; i++) {
+            byte t[] = new byte[HASH_LENGTH];
+            for (int j = 0; j < HASH_LENGTH; j++)
+                t[j] = (byte)((i >> j) & 0xFF);
+            Hash cur = new Hash(t);
+            if (!local._xorCache.containsKey(cur)) {
+                _log.error("checking the cache, we do NOT have " 
+                           + DataHelper.toHexString(cur.getData()));
+                return;
+            }
+        }
+        _log.debug("Fill check test passed");
+    }
+*********/
+}
diff --git a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java
index fea56423b65ba3674d294a9a2908296652a767b7..9d1c3a432df6e0d62e734cad6ee10bd2222bb4cc 100644
--- a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java
+++ b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java
@@ -2,6 +2,8 @@ package net.i2p.router.transport.ntcp;
 
 import java.io.IOException;
 import java.net.InetSocketAddress;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
 import java.nio.channels.ServerSocketChannel;
 import java.nio.channels.SocketChannel;
 import java.text.DecimalFormat;
@@ -54,6 +56,9 @@ public class NTCPTransport extends TransportImpl {
      */
     private final List<NTCPConnection> _establishing;
 
+    /** this is rarely if ever used, default is to bind to wildcard address */
+    public static final String PROP_BIND_INTERFACE = "i2np.ntcp.bindInterface";
+
     private final NTCPSendFinisher _finisher;
     private long _lastBadSkew;
     private static final long[] RATES = { 10*60*1000 };
@@ -486,15 +491,29 @@ public class NTCPTransport extends TransportImpl {
     /** call from synchronized method */
     private RouterAddress bindAddress() {
         if (_myAddress != null) {
+            InetAddress bindToAddr = null;
+            String bindTo = _context.getProperty(PROP_BIND_INTERFACE);
+            if (bindTo != null) {
+                try {
+                    bindToAddr = InetAddress.getByName(bindTo);
+                } catch (UnknownHostException uhe) {
+                    _log.log(Log.CRIT, "Invalid SSU bind interface specified [" + bindTo + "]", uhe);
+                    // this can be implemented later, just updates some stats
+                    // see udp/UDPTransport.java
+                    //setReachabilityStatus(CommSystemFacade.STATUS_HOSED);
+                    return null;
+                }
+            }
+
             try {
                 ServerSocketChannel chan = ServerSocketChannel.open();
                 chan.configureBlocking(false);
 
                 InetSocketAddress addr = null;
-                //if (bindAllInterfaces())
+                if(bindToAddr==null)
                     addr = new InetSocketAddress(_myAddress.getPort());
-                //else
-                //    addr = new InetSocketAddress(_myAddress.getAddress(), _myAddress.getPort());
+                else
+                    addr = new InetSocketAddress(bindToAddr, _myAddress.getPort());
                 chan.socket().bind(addr);
                 if (_log.shouldLog(Log.INFO))
                     _log.info("Listening on " + addr);