diff --git a/INSTALL.txt b/INSTALL.txt
index 15a962df2aa38a7dcf475348843e93e0b821b993..adbbc5ad51fd29939ed951953970b3c828b03013 100644
--- a/INSTALL.txt
+++ b/INSTALL.txt
@@ -1,10 +1,17 @@
 I2P source installation instructions
 
+Prerequisites to build from source:
+	Java SDK (preferably Sun) 1.5.0 or higher (1.6 recommended)
+	Apache Ant 1.7.0 or higher
+	Optional, For multilanguage support: The xgettext, msgfmt, and msgmerge tools installed
+	from the GNU gettext package http://www.gnu.org/software/gettext/
+
 To build and install I2P from source, you must first build
 and package up the appropriate installer by running:
 
   ant pkg
 
+
 This will produce a few key files:
 * install.jar:    the GUI and console installer
 * i2pinstall.exe: the GUI and console installer wrapped for cross-platform execution
@@ -18,9 +25,6 @@ Or run the GUI installer:
 
 Or move the update file into an existing installation directory and restart.
 
-You will need to have ant installed from http://ant.apache.org/
-(1.7.0 or newer)
-
 Supported JVMs:
   Windows: Latest available from http://java.sun.com/ (1.5+ supported)
   Linux:   Latest available from http://java.sun.com/ (1.5+ supported)
diff --git a/README.txt b/README.txt
index 3aa2141bb475542b5c49b3a1586c3eb7560ba7da..c5ddc12bd89a529fe40e30534ee200446f824b73 100644
--- a/README.txt
+++ b/README.txt
@@ -1,11 +1,13 @@
 Prerequisites to build from source:
 	Java SDK (preferably Sun) 1.5.0 or higher (1.6 recommended)
 	Apache Ant 1.7.0 or higher
+	Optional, For multilanguage support: The xgettext, msgfmt, and msgmerge tools installed
+	from the GNU gettext package http://www.gnu.org/software/gettext/
 
 To build:
 	ant pkg
 	Run 'ant' with no arguments to see other build options.
-	See http://www.i2p2.de/download.html for installation instructions.
+	See INSTALL.txt or http://www.i2p2.de/download.html for installation instructions.
 
 Documentation:
 	http://www.i2p2.de/
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/NewsFetcher.java b/apps/routerconsole/java/src/net/i2p/router/web/NewsFetcher.java
index 973357baacc8f2204d0bbf405aed984b9dcd8047..8140282d19f491b17f57d38b10c5a1ba0d19a891 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/NewsFetcher.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/NewsFetcher.java
@@ -179,7 +179,7 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
             if (get.fetch()) {
                 String lastmod = get.getLastModified();
                 if (lastmod != null) {
-                    if (!(_context instanceof RouterContext)) return;
+                    if (!(_context.isRouterContext())) return;
                     long modtime = parse822Date(lastmod);
                     if (modtime <= 0) return;
                     String lastUpdate = _context.getProperty(UpdateHandler.PROP_LAST_UPDATE_TIME);
@@ -310,7 +310,7 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
             if (_log.shouldLog(Log.DEBUG))
                 _log.debug("Policy requests update, so we update");
             UpdateHandler handler = null;
-            if (_context instanceof RouterContext) {
+            if (_context.isRouterContext()) {
                 handler = new UpdateHandler((RouterContext)_context);
             } else {
                 List contexts = RouterContext.listContexts();
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/StatsGenerator.java b/apps/routerconsole/java/src/net/i2p/router/web/StatsGenerator.java
index 9ac1ae54225ed2e9b9b6c70677d78e6b437bd405..4ddc6e6f453a89953b69d63922d18bfdce72cf77 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/StatsGenerator.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/StatsGenerator.java
@@ -40,7 +40,7 @@ public class StatsGenerator {
             String group = (String)entry.getKey();
             Set stats = (Set)entry.getValue();
             buf.append("<option value=\"/stats.jsp#").append(group).append("\">");
-            buf.append(group).append("</option>\n");
+            buf.append(_(group)).append("</option>\n");
             for (Iterator statIter = stats.iterator(); statIter.hasNext(); ) {
                 String stat = (String)statIter.next();
                 buf.append("<option value=\"/stats.jsp#");
@@ -52,7 +52,7 @@ public class StatsGenerator {
             out.write(buf.toString());
             buf.setLength(0);
         }
-        buf.append("</select> <input type=\"submit\" value=\"GO\" />");
+        buf.append("</select> <input type=\"submit\" value=\"").append(_("GO")).append("\" />");
         buf.append("</form>");
         
         buf.append(_("Statistics gathered during this router's uptime")).append(" (");
@@ -69,7 +69,7 @@ public class StatsGenerator {
             buf.append("<h3><a name=\"");
             buf.append(group);
             buf.append("\">");
-            buf.append(group);
+            buf.append(_(group));
             buf.append("</a></h3>");
             buf.append("<ul>");
             out.write(buf.toString());
@@ -104,7 +104,7 @@ public class StatsGenerator {
         for (int i = 0; i < periods.length; i++) {
             if (periods[i] > uptime)
                 break;
-            renderPeriod(buf, periods[i], "frequency");
+            renderPeriod(buf, periods[i], _("frequency"));
             Frequency curFreq = freq.getFrequency(periods[i]);
             buf.append(" <i>avg per period:</i> (");
             buf.append(num(curFreq.getAverageEventsPerPeriod()));
@@ -138,7 +138,7 @@ public class StatsGenerator {
             buf.append("</i><br>");
         }
         if (rate.getLifetimeEventCount() <= 0) {
-            buf.append("No lifetime events<br>\n");
+            buf.append(_("No lifetime events")).append("<br>\n");
             return;
         }
         long now = _context.clock().now();
@@ -150,9 +150,9 @@ public class StatsGenerator {
             if (curRate.getLastCoalesceDate() <= curRate.getCreationDate())
                 break;
             buf.append("<li>");
-            renderPeriod(buf, periods[i], "rate");
+            renderPeriod(buf, periods[i], _("rate"));
             if (curRate.getLastEventCount() > 0) {
-                buf.append( "<i>avg value:</i> (");
+                buf.append( "<i>").append(_("avg value")).append(":</i> (");
                 buf.append(num(curRate.getAverageValue()));
                 buf.append(" peak ");
                 buf.append(num(curRate.getExtremeAverageValue()));
@@ -181,21 +181,21 @@ public class StatsGenerator {
                     buf.append(num(curRate.getExtremeSaturationLimit()));
                     buf.append(")");
                 }
-                buf.append(" <i>events:</i> ");
+                buf.append(" <i>").append(_("events")).append(":</i> ");
                 buf.append(curRate.getLastEventCount());
                 buf.append(" <i>in this period which ended:</i> ");
                 buf.append(DataHelper.formatDuration(now - curRate.getLastCoalesceDate()));
                 buf.append(" ago ");
             } else {
-                buf.append(" <i>No events</i> ");
+                buf.append(" <i>").append(_("No events")).append("</i> ");
             }
             long numPeriods = curRate.getLifetimePeriods();
             if (numPeriods > 0) {
                 double avgFrequency = curRate.getLifetimeEventCount() / (double)numPeriods;
                 double peakFrequency = curRate.getExtremeEventCount();
-                buf.append(" (lifetime average: ");
+                buf.append(" (").append(_("lifetime average")).append(": ");
                 buf.append(num(avgFrequency));
-                buf.append(", peak average: ");
+                buf.append(", ").append(_("peak average")).append(": ");
                 buf.append(curRate.getExtremeEventCount());
                 buf.append(")");
             }
@@ -213,7 +213,7 @@ public class StatsGenerator {
             buf.append("</li>\n");
         }
         // Display the strict average
-        buf.append("<li><b>lifetime average value:</b> ");
+        buf.append("<li><b>").append(_("lifetime average value")).append(":</b> ");
         buf.append(num(rate.getLifetimeAverageValue()));
         buf.append(" over ");
         buf.append(rate.getLifetimeEventCount());
@@ -240,4 +240,9 @@ public class StatsGenerator {
     private String _(String s) {
         return Messages.getString(s, _context);
     }
+
+    /** translate a string */
+    private String _(String s, Object o) {
+        return Messages.getString(s, o, _context);
+    }
 }
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java
index 1fc6c31b7096df0094e0f2942fccbf333e2264b1..516855e2db64279485a8ba8e2aafd139124a7fee 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java
@@ -62,6 +62,9 @@ public class SummaryHelper extends HelperBase {
             return DataHelper.formatDuration(router.getUptime());
     }
     
+/**
+    this displayed offset, not skew - now handled in reachability()
+
     private String timeSkew() {
         if (_context == null) return "";
         //if (!_context.clock().getUpdatedSuccessfully())
@@ -72,6 +75,7 @@ public class SummaryHelper extends HelperBase {
             return "";
         return " (" + DataHelper.formatDuration(diff) + " " + _("skew") + ")";
     }
+**/
     
     public boolean allowReseed() {
         return _context.netDb().isInitialized() &&
@@ -83,15 +87,20 @@ public class SummaryHelper extends HelperBase {
     public int getAllPeers() { return Math.max(_context.netDb().getKnownRouters() - 1, 0); }
     
     public String getReachability() {
-        return reachability() + timeSkew();
+        return reachability(); // + timeSkew();
     }
 
     private String reachability() {
         if (_context.router().getUptime() > 60*1000 && (!_context.router().gracefulShutdownInProgress()) &&
             !_context.clientManager().isAlive())
             return _("ERR-Client Manager I2CP Error - check logs");  // not a router problem but the user should know
-        if (!_context.clock().getUpdatedSuccessfully())
-            return _("ERR-ClockSkew");
+        // Warn based on actual skew from peers, not update status, so if we successfully offset
+        // the clock, we don't complain.
+        //if (!_context.clock().getUpdatedSuccessfully())
+        Long skew = _context.commSystem().getFramedAveragePeerClockSkew(33);
+        // Display the actual skew, not the offset
+        if (skew != null && Math.abs(skew.longValue()) > 45)
+            return _("ERR-Clock Skew of {0}", DataHelper.formatDuration(Math.abs(skew.longValue()) * 1000));
         if (_context.router().isHidden())
             return _("Hidden");
 
@@ -118,7 +127,9 @@ public class SummaryHelper extends HelperBase {
             default:
                 ra = _context.router().getRouterInfo().getTargetAddress("SSU");
                 if (ra == null && _context.router().getUptime() > 5*60*1000) {
-                    if (_context.getProperty(ConfigNetHelper.PROP_I2NP_NTCP_HOSTNAME) == null ||
+                    if (getActivePeers() <= 0)
+                        return _("ERR-No Active Peers, Check Network Connection and Firewall");
+                    else if (_context.getProperty(ConfigNetHelper.PROP_I2NP_NTCP_HOSTNAME) == null ||
                         _context.getProperty(ConfigNetHelper.PROP_I2NP_NTCP_PORT) == null)
                         return _("ERR-UDP Disabled and Inbound TCP host/port not set");
                     else
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/TunnelRenderer.java b/apps/routerconsole/java/src/net/i2p/router/web/TunnelRenderer.java
index 71aca046c339377c3268e625f05188f8bfa62add..523b4d3855bfae4fc78e4c31cfcddf5953396c34 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/TunnelRenderer.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/TunnelRenderer.java
@@ -233,7 +233,7 @@ public class TunnelRenderer {
         Collections.sort(peerList, new CountryComparator(this._context.commSystem()));
 
         out.write("<h2><a name=\"peers\"></a>" + _("Tunnel Counts By Peer") + "</h2>\n");
-        out.write("<table><tr><th>" + _("Peer") + "</th><th>" + _("Expl. + Client") + "</th><th>" + _("% of total") + "</th><th>" + _("Part. from + to") + "</th><th>" + _("% of total") + "</th></tr>\n");
+        out.write("<table><tr><th>" + _("Peer") + "</th><th>" + _("Our Tunnels") + "</th><th>" + _("% of total") + "</th><th>" + _("Participating Tunnels") + "</th><th>" + _("% of total") + "</th></tr>\n");
         for (Hash h : peerList) {
              out.write("<tr> <td class=\"cells\" align=\"center\">");
              out.write(netDbLink(h));
@@ -251,7 +251,7 @@ public class TunnelRenderer {
                  out.write('0');
              out.write('\n');
         }
-        out.write("<tr class=\"tablefooter\"> <td align=\"center\"><b>" + _("Tunnels") + "</b> <td align=\"center\"><b>" + tunnelCount);
+        out.write("<tr class=\"tablefooter\"> <td align=\"center\"><b>" + _("Totals") + "</b> <td align=\"center\"><b>" + tunnelCount);
         out.write("</b> <td>&nbsp;</td> <td align=\"center\"><b>" + partCount);
         out.write("</b> <td>&nbsp;</td></tr></table></div>\n");
     }
diff --git a/apps/routerconsole/java/strings/Strings.java b/apps/routerconsole/java/strings/Strings.java
index cddec4197baf89b3443ef6f9f0223b78bd4238fd..0805d479a119730e809bcd2d9a523af8194872d0 100644
--- a/apps/routerconsole/java/strings/Strings.java
+++ b/apps/routerconsole/java/strings/Strings.java
@@ -52,5 +52,24 @@ class Dummy {
         _("dark");
         _("light");
         _("midnight");        
+
+        // stat groups for stats.jsp
+        _("Bandwidth");
+        _("BandwidthLimiter");
+        _("ClientMessages");
+        _("Encryption");
+        _("i2cp");
+        _("I2PTunnel");
+        _("InNetPool");
+        _("JobQueue");
+        _("NetworkDatabase");
+        _("ntcp");
+        _("Peers");
+        _("Router");
+        _("Stream");
+        _("Throttle");
+        _("Transport");
+        _("Tunnels");
+        _("udp");
     }
 }
diff --git a/core/java/src/net/i2p/I2PAppContext.java b/core/java/src/net/i2p/I2PAppContext.java
index 1cdb6b29709789c43700487370bb2615b8895aa9..974b69b84d79362fd37cabaf2a9cce8ef005972e 100644
--- a/core/java/src/net/i2p/I2PAppContext.java
+++ b/core/java/src/net/i2p/I2PAppContext.java
@@ -722,4 +722,11 @@ public class I2PAppContext {
         return new HashSet(_shutdownTasks);
     }
     
+    /**
+     *  Use this instead of context instanceof RouterContext
+     *  @since 0.7.9
+     */
+    public boolean isRouterContext() {
+        return false;
+    }
 }
diff --git a/core/java/src/net/i2p/stat/RateStat.java b/core/java/src/net/i2p/stat/RateStat.java
index b021d59f4ef0eeec4aa1a2ea2650835a14721894..79ddec51980efdcf689f9f95b4cade1f25223d18 100644
--- a/core/java/src/net/i2p/stat/RateStat.java
+++ b/core/java/src/net/i2p/stat/RateStat.java
@@ -170,6 +170,7 @@ public class RateStat {
         }
     }
 
+/*********
     public static void main(String args[]) {
         RateStat rs = new RateStat("moo", "moo moo moo", "cow trueisms", new long[] { 60 * 1000, 60 * 60 * 1000,
                                                                                      24 * 60 * 60 * 1000});
@@ -206,4 +207,5 @@ public class RateStat {
         } catch (InterruptedException ie) { // nop
         }
     }
+*********/
 }
diff --git a/core/java/src/net/i2p/time/Timestamper.java b/core/java/src/net/i2p/time/Timestamper.java
index e47ecd2599ca66c26f2129d6e37d5946a5e4c483..b1d763a3a731af949e1ab1bc73f77f53d19d012e 100644
--- a/core/java/src/net/i2p/time/Timestamper.java
+++ b/core/java/src/net/i2p/time/Timestamper.java
@@ -189,7 +189,10 @@ public class Timestamper implements Runnable {
         long expectedDelta = 0;
         _wellSynced = false;
         for (int i = 0; i < _concurringServers; i++) {
-            try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
+            if (i > 0) {
+                // this delays startup when net is disconnected or the timeserver list is bad, don't make it too long
+                try { Thread.sleep(2*1000); } catch (InterruptedException ie) {}
+            }
             now = NtpClient.currentTime(serverList);
             long delta = now - _context.clock().now();
             found[i] = delta;
diff --git a/router/java/src/net/i2p/router/Router.java b/router/java/src/net/i2p/router/Router.java
index 7e231097b40cb9ac80e4e472438b918981489fba..4ed70a77227de504f385ad50b2defab6893a8a40 100644
--- a/router/java/src/net/i2p/router/Router.java
+++ b/router/java/src/net/i2p/router/Router.java
@@ -41,6 +41,7 @@ import net.i2p.stat.Rate;
 import net.i2p.stat.RateStat;
 import net.i2p.stat.StatManager;
 import net.i2p.util.FileUtil;
+import net.i2p.util.I2PAppThread;
 import net.i2p.util.I2PThread;
 import net.i2p.util.Log;
 import net.i2p.util.SimpleScheduler;
@@ -201,6 +202,8 @@ public class Router {
         installUpdates();
 
         // Apps may use this as an easy way to determine if they are in the router JVM
+        // But context.isRouterContext() is even easier...
+        // Both of these as of 0.7.9
         System.setProperty("router.version", RouterVersion.VERSION);
 
         // NOW we start all the activity
@@ -228,14 +231,10 @@ public class Router {
             }
         };
         _shutdownHook = new ShutdownHook(_context);
-        _gracefulShutdownDetector = new I2PThread(new GracefulShutdown());
-        _gracefulShutdownDetector.setDaemon(true);
-        _gracefulShutdownDetector.setName("Graceful shutdown hook");
+        _gracefulShutdownDetector = new I2PAppThread(new GracefulShutdown(), "Graceful shutdown hook", true);
         _gracefulShutdownDetector.start();
         
-        I2PThread watchdog = new I2PThread(new RouterWatchdog(_context));
-        watchdog.setName("RouterWatchdog");
-        watchdog.setDaemon(true);
+        Thread watchdog = new I2PAppThread(new RouterWatchdog(_context), "RouterWatchdog", true);
         watchdog.start();
         
     }
@@ -339,7 +338,7 @@ public class Router {
         long waited = System.currentTimeMillis() - before;
         if (_log.shouldLog(Log.INFO))
             _log.info("Waited " + waited + "ms to initialize");
-        
+
         _context.jobQueue().addJob(new StartupJob(_context));
     }
     
diff --git a/router/java/src/net/i2p/router/RouterContext.java b/router/java/src/net/i2p/router/RouterContext.java
index 2a3410348c297f9d7ef5d84780d529b9e87bd94e..8f1c240c35af0f167004437568433d3d63f93e9d 100644
--- a/router/java/src/net/i2p/router/RouterContext.java
+++ b/router/java/src/net/i2p/router/RouterContext.java
@@ -379,4 +379,12 @@ public class RouterContext extends I2PAppContext {
         }
     }
     
+    /**
+     *  Use this instead of context instanceof RouterContext
+     *  @return true
+     *  @since 0.7.9
+     */
+    public boolean isRouterContext() {
+        return true;
+    }
 }
diff --git a/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java b/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java
index f37f01aa267eaf75f8c1de3857090d5ec8d47949..2e93cb3e02e3723fa26e687bbb52207d7aa4aa8d 100644
--- a/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java
+++ b/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java
@@ -15,7 +15,7 @@ import java.util.StringTokenizer;
 import net.i2p.I2PAppContext;
 import net.i2p.router.RouterContext;
 import net.i2p.util.EepGet;
-import net.i2p.util.I2PThread;
+import net.i2p.util.I2PAppThread;
 import net.i2p.util.Log;
 
 /**
@@ -52,13 +52,15 @@ public class Reseeder {
                 return;
             } else {
                 System.setProperty(PROP_INPROGRESS, "true");
-                I2PThread reseed = new I2PThread(_reseedRunner, "Reseed");
+                // set to daemon so it doesn't hang a shutdown
+                Thread reseed = new I2PAppThread(_reseedRunner, "Reseed", true);
                 reseed.start();
             }
         }
 
     }
 
+    /** Todo: translate the messages sent via PROP_STATUS */
     public class ReseedRunner implements Runnable, EepGet.StatusListener {
         private boolean _isRunning;
 
diff --git a/router/java/src/net/i2p/router/peermanager/DBHistory.java b/router/java/src/net/i2p/router/peermanager/DBHistory.java
index a8e084e491c963baba154a92add12ee2ab38b6be..36b9bc9a3015d08fa1e1f3689a2292fd7d726571 100644
--- a/router/java/src/net/i2p/router/peermanager/DBHistory.java
+++ b/router/java/src/net/i2p/router/peermanager/DBHistory.java
@@ -105,6 +105,7 @@ public class DBHistory {
      */
     public RateStat getFailedLookupRate() { return _failedLookupRate; }
     
+    /** not sure how much this is used, to be investigated */
     public RateStat getInvalidReplyRate() { return _invalidReplyRate; }
     
     /**
@@ -115,6 +116,7 @@ public class DBHistory {
     public void lookupSuccessful() {
         _successfulLookups++;
         _failedLookupRate.addData(0, 0);
+        _context.statManager().addRateData("peer.failedLookupRate", 0, 0);
         _lastLookupSuccessful = _context.clock().now();
     }
 
@@ -124,6 +126,7 @@ public class DBHistory {
     public void lookupFailed() {
         _failedLookups++;
         _failedLookupRate.addData(1, 0);
+        _context.statManager().addRateData("peer.failedLookupRate", 1, 0);
         _lastLookupFailed = _context.clock().now();
     }
 
@@ -136,6 +139,7 @@ public class DBHistory {
         // Fixme, redefined this to include both lookup and store fails,
         // need to fix the javadocs
         _failedLookupRate.addData(0, 0);
+        _context.statManager().addRateData("peer.failedLookupRate", 0, 0);
         _lastStoreSuccessful = _context.clock().now();
     }
 
@@ -275,9 +279,9 @@ public class DBHistory {
     
     private void createRates(String statGroup) {
         if (_failedLookupRate == null)
-            _failedLookupRate = new RateStat("dbHistory.failedLookupRate", "How often does this peer to respond to a lookup?", statGroup, new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l });
+            _failedLookupRate = new RateStat("dbHistory.failedLookupRate", "How often does this peer to respond to a lookup?", statGroup, new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l });
         if (_invalidReplyRate == null)
-            _invalidReplyRate = new RateStat("dbHistory.invalidReplyRate", "How often does this peer give us a bad (nonexistant, forged, etc) peer?", statGroup, new long[] { 30*60*1000l, 60*60*1000l, 24*60*60*1000l });
+            _invalidReplyRate = new RateStat("dbHistory.invalidReplyRate", "How often does this peer give us a bad (nonexistant, forged, etc) peer?", statGroup, new long[] { 30*60*1000l });
         _failedLookupRate.setStatLog(_context.statManager().getStatLog());
         _invalidReplyRate.setStatLog(_context.statManager().getStatLog());
     }
diff --git a/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java b/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java
index 20f0fba3d4b5977c2247efab62056070b3f98a9c..d249dc9687c77116cf3c4a8ea3220958b93e34b7 100644
--- a/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java
+++ b/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java
@@ -97,10 +97,10 @@ public class ProfileOrganizer {
         _log = context.logManager().getLog(ProfileOrganizer.class);
         _comp = new InverseCapacityComparator();
         _fastPeers = new HashMap(16);
-        _highCapacityPeers = new HashMap(16);
+        _highCapacityPeers = new HashMap(32);
         _wellIntegratedPeers = new HashMap(16);
-        _notFailingPeers = new HashMap(64);
-        _notFailingPeersList = new ArrayList(64);
+        _notFailingPeers = new HashMap(256);
+        _notFailingPeersList = new ArrayList(256);
         _failingPeers = new HashMap(16);
         _strictCapacityOrder = new TreeSet(_comp);
         _thresholdSpeedValue = 0.0d;
@@ -113,6 +113,8 @@ public class ProfileOrganizer {
         _context.statManager().createRateStat("peer.profileThresholdTime", "How long the reorg takes determining the tier thresholds", "Peers", new long[] { 10*60*1000 });
         _context.statManager().createRateStat("peer.profilePlaceTime", "How long the reorg takes placing peers in the tiers", "Peers", new long[] { 10*60*1000 });
         _context.statManager().createRateStat("peer.profileReorgTime", "How long the reorg takes overall", "Peers", new long[] { 10*60*1000 });
+        // used in DBHistory
+        _context.statManager().createRateStat("peer.failedLookupRate", "DB Lookup fail rate", "Peers", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l });
     }
     
     private void getReadLock() {
diff --git a/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java b/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java
index 6a7eaca84b4d1e6ae7423a0e6172de229bfbc6f5..f1460b9f7a2bdd09c9f87ba776746e3ee58ef99b 100644
--- a/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java
+++ b/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java
@@ -77,31 +77,26 @@ public class CommSystemFacadeImpl extends CommSystemFacade {
     public boolean haveHighOutboundCapacity() { return (_manager == null ? false : _manager.haveHighOutboundCapacity()); } 
     
     /**
-     * Framed average clock skew of connected peers in seconds, or null if we cannot answer.
+     * Framed average clock skew of connected peers in seconds, or the clock offset if we cannot answer.
      * Average is calculated over the middle "percentToInclude" peers.
      */
     @Override
     public Long getFramedAveragePeerClockSkew(int percentToInclude) {
         if (_manager == null) {
-            if (_log.shouldLog(Log.INFO))
-                _log.info("Returning null for framed averege peer clock skew (no transport manager)!");
-            return null;
+            // round toward zero
+            return Long.valueOf(_context.clock().getOffset() / 1000);
         }
         Vector skews = _manager.getClockSkews();
         if (skews == null) {
-            if (_log.shouldLog(Log.ERROR))
-                _log.error("Returning null for framed average peer clock skew (no data)!");
-            return null;
+            return Long.valueOf(_context.clock().getOffset() / 1000);
         }
-        if (skews.size() < 20) {
-            if (_log.shouldLog(Log.WARN))
-                _log.warn("Returning null for framed average peer clock skew (only " + skews.size() + " peers)!");
-            return null;
+        if (skews.size() < 5) {
+            return Long.valueOf(_context.clock().getOffset() / 1000);
         }
         // Going to calculate, sort them
         Collections.sort(skews);
         // Calculate frame size
-        int frameSize = (skews.size() * percentToInclude / 100);
+        int frameSize = Math.min((skews.size() * percentToInclude / 100), 2);
         int first = (skews.size() / 2) - (frameSize / 2);
         int last = (skews.size() / 2) + (frameSize / 2);
         // Sum skew values
@@ -112,11 +107,8 @@ public class CommSystemFacadeImpl extends CommSystemFacade {
                 _log.debug("Adding clock skew " + i + " valued " + value + " s.");
             sum = sum + value;
         }
-        // Calculate average
-        Long framedAverageClockSkew = new Long(sum / frameSize);
-        if (_log.shouldLog(Log.INFO))
-            _log.info("Our framed average peer clock skew is " + framedAverageClockSkew + " s.");
-        return framedAverageClockSkew;
+        // Calculate average (round toward zero)
+        return Long.valueOf(sum / frameSize);
     }
     
     public List getBids(OutNetMessage msg) {
diff --git a/router/java/src/net/i2p/router/tunnel/BatchedPreprocessor.java b/router/java/src/net/i2p/router/tunnel/BatchedPreprocessor.java
index 37c173c75ce0c58a51b3a8df55b9e09fd5c1574c..30fbbdd27bbbdc371479b6e827a0d713490ec0a2 100644
--- a/router/java/src/net/i2p/router/tunnel/BatchedPreprocessor.java
+++ b/router/java/src/net/i2p/router/tunnel/BatchedPreprocessor.java
@@ -2,7 +2,7 @@ package net.i2p.router.tunnel;
 
 import java.util.List;
 
-import net.i2p.I2PAppContext;
+import net.i2p.router.RouterContext;
 import net.i2p.util.Log;
 
 /**
@@ -45,11 +45,10 @@ import net.i2p.util.Log;
  *   }
  */
 public class BatchedPreprocessor extends TrivialPreprocessor {
-    private Log _log;
     private long _pendingSince;
     private String _name;
     
-    public BatchedPreprocessor(I2PAppContext ctx, String name) {
+    public BatchedPreprocessor(RouterContext ctx, String name) {
         super(ctx);
         _log = ctx.logManager().getLog(BatchedPreprocessor.class);
         _name = name;
@@ -98,8 +97,9 @@ public class BatchedPreprocessor extends TrivialPreprocessor {
         return rv;
     }
     
+    /* See TunnelGateway.QueuePreprocessor for Javadoc */ 
     @Override
-    public boolean preprocessQueue(List pending, TunnelGateway.Sender sender, TunnelGateway.Receiver rec) {
+    public boolean preprocessQueue(List<TunnelGateway.Pending> pending, TunnelGateway.Sender sender, TunnelGateway.Receiver rec) {
         StringBuilder timingBuf = null;
         if (_log.shouldLog(Log.DEBUG)) {
             _log.debug("Preprocess queue with " + pending.size() + " to send");
@@ -116,12 +116,15 @@ public class BatchedPreprocessor extends TrivialPreprocessor {
         int batchCount = 0;
         int beforeLooping = pending.size();
         
+        // loop until the queue is empty
         while (pending.size() > 0) {
             int allocated = 0;
             long beforePendingLoop = System.currentTimeMillis();
+
+            // loop until we fill up a single message
             for (int i = 0; i < pending.size(); i++) {
                 long pendingStart = System.currentTimeMillis();
-                TunnelGateway.Pending msg = (TunnelGateway.Pending)pending.get(i);
+                TunnelGateway.Pending msg = pending.get(i);
                 int instructionsSize = getInstructionsSize(msg);
                 instructionsSize += getInstructionAugmentationSize(msg, allocated, instructionsSize);
                 int curWanted = msg.getData().length - msg.getOffset() + instructionsSize;
@@ -135,7 +138,7 @@ public class BatchedPreprocessor extends TrivialPreprocessor {
                         // the instructions alone exceed the size, so we won't get any
                         // of the message into it.  don't include it
                         i--;
-                        msg = (TunnelGateway.Pending)pending.get(i);
+                        msg = pending.get(i);
                         allocated -= curWanted;
                         if (_log.shouldLog(Log.DEBUG))
                             _log.debug("Pushback of " + curWanted + " (message " + (i+1) + " in " + pending + ")");
@@ -144,6 +147,8 @@ public class BatchedPreprocessor extends TrivialPreprocessor {
                         long waited = _context.clock().now() - _pendingSince;
                         _context.statManager().addRateData("tunnel.batchDelaySent", pending.size(), waited);
                     }
+
+                    // Send the message
                     long beforeSend = System.currentTimeMillis();
                     _pendingSince = 0;
                     send(pending, 0, i, sender, rec);
@@ -154,8 +159,9 @@ public class BatchedPreprocessor extends TrivialPreprocessor {
                                   + " (last complete? " + (msg.getOffset() >= msg.getData().length) 
                                   + ", off=" + msg.getOffset() + ", count=" + pending.size() + ")");
 
+                    // Remove what we sent from the pending queue
                     for (int j = 0; j < i; j++) {
-                        TunnelGateway.Pending cur = (TunnelGateway.Pending)pending.remove(0);
+                        TunnelGateway.Pending cur = pending.remove(0);
                         if (cur.getOffset() < cur.getData().length)
                             throw new IllegalArgumentException("i=" + i + " j=" + j + " off=" + cur.getOffset() 
                                                                + " len=" + cur.getData().length + " alloc=" + allocated);
@@ -167,7 +173,7 @@ public class BatchedPreprocessor extends TrivialPreprocessor {
                     }
                     if (msg.getOffset() >= msg.getData().length) {
                         // ok, this last message fit perfectly, remove it too
-                        TunnelGateway.Pending cur = (TunnelGateway.Pending)pending.remove(0);
+                        TunnelGateway.Pending cur = pending.remove(0);
                         if (timingBuf != null)
                             timingBuf.append(" sent perfect fit " + cur).append(".");
                         notePreprocessing(cur.getMessageId(), cur.getFragmentNumber(), msg.getData().length, msg.getMessageIds(), "flushed tail, remaining: " + pending);
@@ -186,18 +192,18 @@ public class BatchedPreprocessor extends TrivialPreprocessor {
                                          + "/" + (beforeSend-start)
                                          + " pending current " + (pendingEnd-pendingStart)).append(".");
                     break;
-                }
+                }  // if >= full size
                 if (timingBuf != null)
                     timingBuf.append(" After pending loop " + (System.currentTimeMillis()-beforePendingLoop)).append(".");
-            }
+            }  // for
             
             long afterCleared = System.currentTimeMillis();
             if (_log.shouldLog(Log.INFO))
                 display(allocated, pending, "after looping to clear " + (beforeLooping - pending.size()));
             long afterDisplayed = System.currentTimeMillis();
             if (allocated > 0) {
-                // after going through the entire pending list, we still don't
-                // have enough data to send a full message
+                // After going through the entire pending list, we have only a partial message.
+                // We might flush it or might not, but we are returning either way.
 
                 if ( (pending.size() > FORCE_BATCH_FLUSH) || ( (_pendingSince > 0) && (getDelayAmount() <= 0) ) ) {
                     // not even a full message, but we want to flush it anyway
@@ -209,9 +215,10 @@ public class BatchedPreprocessor extends TrivialPreprocessor {
                     send(pending, 0, pending.size()-1, sender, rec);
                     _context.statManager().addRateData("tunnel.batchSmallFragments", FULL_SIZE - allocated, 0);
                     
+                    // Remove everything in the message from the pending queue
                     int beforeSize = pending.size();
                     for (int i = 0; i < pending.size(); i++) {
-                        TunnelGateway.Pending cur = (TunnelGateway.Pending)pending.get(i);
+                        TunnelGateway.Pending cur = pending.get(i);
                         if (cur.getOffset() >= cur.getData().length) {
                             pending.remove(i);
                             notePreprocessing(cur.getMessageId(), cur.getFragmentNumber(), cur.getData().length, cur.getMessageIds(), "flushed remaining");
@@ -246,7 +253,9 @@ public class BatchedPreprocessor extends TrivialPreprocessor {
                         }
                         return false;
                     }
+                    // won't get here, we returned
                 } else {
+                     // We didn't flush. Note that the messages remain on the pending list.
                     _context.statManager().addRateData("tunnel.batchDelay", pending.size(), 0);
                     if (_pendingSince <= 0)
                         _pendingSince = _context.clock().now();
@@ -262,14 +271,15 @@ public class BatchedPreprocessor extends TrivialPreprocessor {
                     }
                     return true;
                 }
+                // won't get here, we returned
             } else {
                 // ok, we sent some, but haven't gone back for another 
                 // pass yet.  keep looping
                 
                 if (timingBuf != null)
                     timingBuf.append(" Keep looping");
-            }
-        }
+            }  // if allocated
+        }  // while
         
         if (_log.shouldLog(Log.DEBUG))
             _log.debug("Sent everything on the list (pending=" + pending.size() + ")");
@@ -283,7 +293,7 @@ public class BatchedPreprocessor extends TrivialPreprocessor {
         return false;
     }
     
-    private void display(long allocated, List pending, String title) {
+    private void display(long allocated, List<TunnelGateway.Pending> pending, String title) {
         if (_log.shouldLog(Log.INFO)) {
             long highestDelay = 0;
             StringBuilder buf = new StringBuilder();
@@ -294,7 +304,7 @@ public class BatchedPreprocessor extends TrivialPreprocessor {
             if (_pendingSince > 0)
                 buf.append(" delay: ").append(getDelayAmount(false));
             for (int i = 0; i < pending.size(); i++) {
-                TunnelGateway.Pending curPending = (TunnelGateway.Pending)pending.get(i);
+                TunnelGateway.Pending curPending = pending.get(i);
                 buf.append(" pending[").append(i).append("]: ");
                 buf.append(curPending.getOffset()).append("/").append(curPending.getData().length).append('/');
                 buf.append(curPending.getLifetime());
@@ -314,7 +324,7 @@ public class BatchedPreprocessor extends TrivialPreprocessor {
      * @param startAt first index in pending to send (inclusive)
      * @param sendThrough last index in pending to send (inclusive)
      */
-    protected void send(List pending, int startAt, int sendThrough, TunnelGateway.Sender sender, TunnelGateway.Receiver rec) {
+    protected void send(List<TunnelGateway.Pending> pending, int startAt, int sendThrough, TunnelGateway.Sender sender, TunnelGateway.Receiver rec) {
         if (_log.shouldLog(Log.DEBUG))
             _log.debug("Sending " + startAt + ":" + sendThrough + " out of " + pending);
         byte preprocessed[] = _dataCache.acquire().getData();
@@ -346,7 +356,7 @@ public class BatchedPreprocessor extends TrivialPreprocessor {
         
         long msgId = sender.sendPreprocessed(preprocessed, rec);
         for (int i = 0; i < pending.size(); i++) {
-            TunnelGateway.Pending cur = (TunnelGateway.Pending)pending.get(i);
+            TunnelGateway.Pending cur = pending.get(i);
             cur.addMessageId(msgId);
         }
         if (_log.shouldLog(Log.DEBUG))
@@ -359,9 +369,9 @@ public class BatchedPreprocessor extends TrivialPreprocessor {
      *
      * @return new offset into the target for further bytes to be written
      */
-    private int writeFragments(List pending, int startAt, int sendThrough, byte target[], int offset) {
+    private int writeFragments(List<TunnelGateway.Pending> pending, int startAt, int sendThrough, byte target[], int offset) {
         for (int i = startAt; i <= sendThrough; i++) {
-            TunnelGateway.Pending msg = (TunnelGateway.Pending)pending.get(i);
+            TunnelGateway.Pending msg = pending.get(i);
             int prevOffset = offset;
             if (msg.getOffset() == 0) {
                 offset = writeFirstFragment(msg, target, offset);
diff --git a/router/java/src/net/i2p/router/tunnel/BatchedRouterPreprocessor.java b/router/java/src/net/i2p/router/tunnel/BatchedRouterPreprocessor.java
index 2e5e988855b5d05b072a72ae15fc28d0e0d4d356..6af65e88e2e23991812d1678ac42bb9657016797 100644
--- a/router/java/src/net/i2p/router/tunnel/BatchedRouterPreprocessor.java
+++ b/router/java/src/net/i2p/router/tunnel/BatchedRouterPreprocessor.java
@@ -11,9 +11,9 @@ import net.i2p.router.RouterContext;
  *
  */
 public class BatchedRouterPreprocessor extends BatchedPreprocessor {
-    private RouterContext _routerContext;
+    protected RouterContext _routerContext;
     private TunnelCreatorConfig _config;
-    private HopConfig _hopConfig;
+    protected HopConfig _hopConfig;
     
     /** 
      * How frequently should we flush non-full messages, in milliseconds
@@ -79,7 +79,7 @@ public class BatchedRouterPreprocessor extends BatchedPreprocessor {
     }
     
     @Override
-    protected void notePreprocessing(long messageId, int numFragments, int totalLength, List messageIds, String msg) {
+    protected void notePreprocessing(long messageId, int numFragments, int totalLength, List<Long> messageIds, String msg) {
         if (_config != null)
             _routerContext.messageHistory().fragmentMessage(messageId, numFragments, totalLength, messageIds, _config, msg);
         else
diff --git a/router/java/src/net/i2p/router/tunnel/FragmentHandler.java b/router/java/src/net/i2p/router/tunnel/FragmentHandler.java
index 43e1d13144de93911a3c84eccdb6c1fac94a16ff..f294d5c7855359282067dadb0afc3d2621177420 100644
--- a/router/java/src/net/i2p/router/tunnel/FragmentHandler.java
+++ b/router/java/src/net/i2p/router/tunnel/FragmentHandler.java
@@ -21,6 +21,65 @@ import net.i2p.util.SimpleTimer;
  * Handle fragments at the endpoint of a tunnel, peeling off fully completed 
  * I2NPMessages when they arrive, and dropping fragments if they take too long
  * to arrive.
+ *
+ * From tunnel-alt.html:
+
+<p>When the gateway wants to deliver data through the tunnel, it first
+gathers zero or more <a href="i2np.html">I2NP</a> messages, selects how much padding will be used, 
+fragments it across the necessary number of 1KB tunnel messages, and decides how
+each I2NP message should be handled by the tunnel endpoint, encoding that
+data into the raw tunnel payload:</p>
+<ul>
+<li>the first 4 bytes of the SHA256 of (the remaining preprocessed data concatenated 
+    with the IV), using the IV as will be seen on the tunnel endpoint (for
+    outbound tunnels), or the IV as was seen on the tunnel gateway (for inbound
+    tunnels) (see below for IV processing).</li>
+<li>0 or more bytes containing random nonzero integers</li>
+<li>1 byte containing 0x00</li>
+<li>a series of zero or more { instructions, message } pairs</li>
+</ul>
+
+<p>The instructions are encoded with a single control byte, followed by any
+necessary additional information.  The first bit in that control byte determines
+how the remainder of the header is interpreted - if it is not set, the message 
+is either not fragmented or this is the first fragment in the message.  If it is
+set, this is a follow on fragment.</p>
+
+<p>With the first bit being 0, the instructions are:</p>
+<ul>
+<li>1 byte control byte:<pre>
+      bit 0: is follow on fragment?  (1 = true, 0 = false, must be 0)
+   bits 1-2: delivery type
+             (0x0 = LOCAL, 0x01 = TUNNEL, 0x02 = ROUTER)
+      bit 3: delay included?  (1 = true, 0 = false)
+      bit 4: fragmented?  (1 = true, 0 = false)
+      bit 5: extended options?  (1 = true, 0 = false)
+   bits 6-7: reserved</pre></li>
+<li>if the delivery type was TUNNEL, a 4 byte tunnel ID</li>
+<li>if the delivery type was TUNNEL or ROUTER, a 32 byte router hash</li>
+<li>if the delay included flag is true, a 1 byte value:<pre>
+      bit 0: type (0 = strict, 1 = randomized)
+   bits 1-7: delay exponent (2^value minutes)</pre></li>
+<li>if the fragmented flag is true, a 4 byte message ID</li>
+<li>if the extended options flag is true:<pre>
+   = a 1 byte option size (in bytes)
+   = that many bytes</pre></li>
+<li>2 byte size of the I2NP message or this fragment</li>
+</ul>
+
+<p>If the first bit being 1, the instructions are:</p>
+<ul>
+<li>1 byte control byte:<pre>
+      bit 0: is follow on fragment?  (1 = true, 0 = false, must be 1)
+   bits 1-6: fragment number
+      bit 7: is last? (1 = true, 0 = false)</pre></li>
+<li>4 byte message ID (same one defined in the first fragment)</li>
+<li>2 byte size of this fragment</li>
+</ul>
+
+<p>The I2NP message is encoded in its standard form, and the 
+preprocessed payload must be padded to a multiple of 16 bytes.</p>
+
  *
  */
 public class FragmentHandler {
@@ -149,7 +208,7 @@ public class FragmentHandler {
                 if (_log.shouldLog(Log.WARN))
                     _log.warn("cannot verify, going past the end [off=" 
                               + offset + " len=" + length + " paddingEnd=" 
-                              + paddingEnd + " data:\n"
+                              + paddingEnd + " data: "
                               + Base64.encode(preprocessed, offset, length));
                 return false;
             }
@@ -165,21 +224,19 @@ public class FragmentHandler {
             _log.debug("endpoint IV: " + Base64.encode(preV, validLength - HopProcessor.IV_LENGTH, HopProcessor.IV_LENGTH));
         
         Hash v = _context.sha().calculateHash(preV, 0, validLength);
+        _validateCache.release(ba);
         
-        //Hash v = _context.sha().calculateHash(preV, 0, validLength);
         boolean eq = DataHelper.eq(v.getData(), 0, preprocessed, offset + HopProcessor.IV_LENGTH, 4);
         if (!eq) {
-            if (_log.shouldLog(Log.WARN))
-                _log.warn("Corrupt tunnel message - verification fails: \n" + Base64.encode(preprocessed, offset+HopProcessor.IV_LENGTH, 4)
-                           + "\n" + Base64.encode(v.getData(), 0, 4));
-            if (_log.shouldLog(Log.WARN))
-                _log.warn("nomatching endpoint: # pad bytes: " + (paddingEnd-(HopProcessor.IV_LENGTH+4)-1) + "\n" 
-                           + " offset=" + offset + " length=" + length + " paddingEnd=" + paddingEnd
+            if (_log.shouldLog(Log.WARN)) {
+                _log.warn("Corrupt tunnel message - verification fails: " + Base64.encode(preprocessed, offset+HopProcessor.IV_LENGTH, 4)
+                           + " != " + Base64.encode(v.getData(), 0, 4));
+                _log.warn("No matching endpoint: # pad bytes: " + (paddingEnd-(HopProcessor.IV_LENGTH+4)-1)
+                           + " offset=" + offset + " length=" + length + " paddingEnd=" + paddingEnd + ' '
                            + Base64.encode(preprocessed, offset, length));
+            }
         }
         
-        _validateCache.release(ba);
-        
         if (eq) {
             int excessPadding = paddingEnd - (HopProcessor.IV_LENGTH + 4 + 1);
             if (excessPadding > 0) // suboptimal fragmentation
diff --git a/router/java/src/net/i2p/router/tunnel/InboundEndpointProcessor.java b/router/java/src/net/i2p/router/tunnel/InboundEndpointProcessor.java
index 6c572cb426ab90bc7267bbf785d547f8a9017311..7376a5046692207b666f6edccbe3d70c34c9af9f 100644
--- a/router/java/src/net/i2p/router/tunnel/InboundEndpointProcessor.java
+++ b/router/java/src/net/i2p/router/tunnel/InboundEndpointProcessor.java
@@ -1,6 +1,5 @@
 package net.i2p.router.tunnel;
 
-import net.i2p.I2PAppContext;
 import net.i2p.data.ByteArray;
 import net.i2p.data.Hash;
 import net.i2p.router.RouterContext;
@@ -16,7 +15,7 @@ import net.i2p.util.Log;
  *
  */
 public class InboundEndpointProcessor {
-    private I2PAppContext _context;
+    private RouterContext _context;
     private Log _log;
     private TunnelCreatorConfig _config;
     private IVValidator _validator;    
@@ -24,10 +23,10 @@ public class InboundEndpointProcessor {
     static final boolean USE_ENCRYPTION = HopProcessor.USE_ENCRYPTION;
     private static final ByteCache _cache = ByteCache.getInstance(128, HopProcessor.IV_LENGTH);
     
-    public InboundEndpointProcessor(I2PAppContext ctx, TunnelCreatorConfig cfg) {
+    public InboundEndpointProcessor(RouterContext ctx, TunnelCreatorConfig cfg) {
         this(ctx, cfg, DummyValidator.getInstance());
     }
-    public InboundEndpointProcessor(I2PAppContext ctx, TunnelCreatorConfig cfg, IVValidator validator) {
+    public InboundEndpointProcessor(RouterContext ctx, TunnelCreatorConfig cfg, IVValidator validator) {
         _context = ctx;
         _log = ctx.logManager().getLog(InboundEndpointProcessor.class);
         _config = cfg;
@@ -73,23 +72,19 @@ public class InboundEndpointProcessor {
         
         _cache.release(ba);
         
-        // now for a little bookkeeping
-        RouterContext ctx = null;
-        if (_context instanceof RouterContext)
-            ctx = (RouterContext)_context;
-        if ( (ctx != null) && (_config.getLength() > 0) ) {
+        if (_config.getLength() > 0) {
             int rtt = 0; // dunno... may not be related to an rtt
             if (_log.shouldLog(Log.DEBUG))
                 _log.debug("Received a " + length + "byte message through tunnel " + _config);
             for (int i = 0; i < _config.getLength(); i++)
-                ctx.profileManager().tunnelDataPushed(_config.getPeer(i), rtt, length);
+                _context.profileManager().tunnelDataPushed(_config.getPeer(i), rtt, length);
             _config.incrementVerifiedBytesTransferred(length);
         }
         
         return true;
     }
     
-    private void decrypt(I2PAppContext ctx, TunnelCreatorConfig cfg, byte iv[], byte orig[], int offset, int length) {
+    private void decrypt(RouterContext ctx, TunnelCreatorConfig cfg, byte iv[], byte orig[], int offset, int length) {
         Log log = ctx.logManager().getLog(OutboundGatewayProcessor.class);
         ByteArray ba = _cache.acquire();
         byte cur[] = ba.getData(); // new byte[HopProcessor.IV_LENGTH]; // so we dont malloc
diff --git a/router/java/src/net/i2p/router/tunnel/TrivialPreprocessor.java b/router/java/src/net/i2p/router/tunnel/TrivialPreprocessor.java
index 4792b14c266bcc66e33e73117288f74dab5d46ad..bd7bc73628c135167d29739138db86beacb81828 100644
--- a/router/java/src/net/i2p/router/tunnel/TrivialPreprocessor.java
+++ b/router/java/src/net/i2p/router/tunnel/TrivialPreprocessor.java
@@ -3,11 +3,11 @@ package net.i2p.router.tunnel;
 import java.util.ArrayList;
 import java.util.List;
 
-import net.i2p.I2PAppContext;
 import net.i2p.data.Base64;
 import net.i2p.data.ByteArray;
 import net.i2p.data.DataHelper;
 import net.i2p.data.Hash;
+import net.i2p.router.RouterContext;
 import net.i2p.util.ByteCache;
 import net.i2p.util.Log;
 
@@ -19,8 +19,8 @@ import net.i2p.util.Log;
  *
  */
 public class TrivialPreprocessor implements TunnelGateway.QueuePreprocessor {
-    protected I2PAppContext _context;
-    private Log _log;
+    protected RouterContext _context;
+    protected Log _log;
     
     public static final int PREPROCESSED_SIZE = 1024;
     protected static final int IV_SIZE = HopProcessor.IV_LENGTH;
@@ -28,7 +28,7 @@ public class TrivialPreprocessor implements TunnelGateway.QueuePreprocessor {
     protected static final ByteCache _ivCache = ByteCache.getInstance(128, IV_SIZE);
     protected static final ByteCache _hashCache = ByteCache.getInstance(128, Hash.HASH_LENGTH);
     
-    public TrivialPreprocessor(I2PAppContext ctx) {
+    public TrivialPreprocessor(RouterContext ctx) {
         _context = ctx;
         _log = ctx.logManager().getLog(TrivialPreprocessor.class);
     }
@@ -41,7 +41,7 @@ public class TrivialPreprocessor implements TunnelGateway.QueuePreprocessor {
      * a delayed flush to clear them
      *
      */
-    public boolean preprocessQueue(List pending, TunnelGateway.Sender sender, TunnelGateway.Receiver rec) {
+    public boolean preprocessQueue(List<TunnelGateway.Pending> pending, TunnelGateway.Sender sender, TunnelGateway.Receiver rec) {
         long begin = System.currentTimeMillis();
         StringBuilder buf = null;
         if (_log.shouldLog(Log.DEBUG)) {
@@ -49,7 +49,7 @@ public class TrivialPreprocessor implements TunnelGateway.QueuePreprocessor {
             buf.append("Trivial preprocessing of ").append(pending.size()).append(" ");
         }
         while (pending.size() > 0) {
-            TunnelGateway.Pending msg = (TunnelGateway.Pending)pending.remove(0);
+            TunnelGateway.Pending msg = pending.remove(0);
             long beforePreproc = System.currentTimeMillis();
             byte preprocessed[][] = preprocess(msg);
             long afterPreproc = System.currentTimeMillis();
@@ -84,7 +84,7 @@ public class TrivialPreprocessor implements TunnelGateway.QueuePreprocessor {
         return false;
     }
     
-    protected void notePreprocessing(long messageId, int numFragments, int totalLength, List messageIds, String msg) {}
+    protected void notePreprocessing(long messageId, int numFragments, int totalLength, List<Long> messageIds, String msg) {}
     
     private byte[][] preprocess(TunnelGateway.Pending msg) {
         List fragments = new ArrayList(1);
diff --git a/router/java/src/net/i2p/router/tunnel/TrivialRouterPreprocessor.java b/router/java/src/net/i2p/router/tunnel/TrivialRouterPreprocessor.java
index ad7322cb61f54a6d5dd3268144f682c98e8ca793..6ddf636e4f77a5e766dafeafdb22e3cf8bd07b0d 100644
--- a/router/java/src/net/i2p/router/tunnel/TrivialRouterPreprocessor.java
+++ b/router/java/src/net/i2p/router/tunnel/TrivialRouterPreprocessor.java
@@ -7,6 +7,7 @@ import net.i2p.router.RouterContext;
 /** 
  * Minor extension to track fragmentation
  *
+ * @deprecated unused
  */
 public class TrivialRouterPreprocessor extends TrivialPreprocessor {
     private RouterContext _routerContext;
@@ -16,7 +17,7 @@ public class TrivialRouterPreprocessor extends TrivialPreprocessor {
         _routerContext = ctx;
     }
 
-    protected void notePreprocessing(long messageId, int numFragments, int totalLength, List messageIds) {
+    protected void notePreprocessing(long messageId, int numFragments, int totalLength, List<Long> messageIds) {
         _routerContext.messageHistory().fragmentMessage(messageId, numFragments, totalLength, messageIds, null);
     }
 }
diff --git a/router/java/src/net/i2p/router/tunnel/TunnelDispatcher.java b/router/java/src/net/i2p/router/tunnel/TunnelDispatcher.java
index 681dadec100e517e05d23b4b9c71512bee1e12d6..527426416e7997855df194d0728ca99e4c9e3ff4 100644
--- a/router/java/src/net/i2p/router/tunnel/TunnelDispatcher.java
+++ b/router/java/src/net/i2p/router/tunnel/TunnelDispatcher.java
@@ -131,17 +131,20 @@ public class TunnelDispatcher implements Service {
                                          new long[] { 60*1000l, 10*60*1000l, 60*60*1000l });
     }
 
+    /** for IBGW */
     private TunnelGateway.QueuePreprocessor createPreprocessor(HopConfig cfg) {
-        if (true)
-            return new BatchedRouterPreprocessor(_context, cfg); 
-        else
-            return new TrivialRouterPreprocessor(_context); 
+        //if (true)
+            return new DroppingBatchedRouterPreprocessor(_context, cfg); 
+        //else
+        //    return new TrivialRouterPreprocessor(_context); 
     }
+
+    /** for OBGW */
     private TunnelGateway.QueuePreprocessor createPreprocessor(TunnelCreatorConfig cfg) {
-        if (true)
+        //if (true)
             return new BatchedRouterPreprocessor(_context, cfg); 
-        else
-            return new TrivialRouterPreprocessor(_context); 
+        //else
+        //    return new TrivialRouterPreprocessor(_context); 
     }
     
     /**
@@ -605,12 +608,17 @@ public class TunnelDispatcher implements Service {
         if (pctDrop <= 0)
             return false;
         // increase the drop probability for OBEP,
+        // (except lower it for tunnel build messages (type 21)),
         // and lower it for IBGW, for network efficiency
         double len = length;
-        if (type.startsWith("OBEP"))
-            len *= 1.5;
-        else if (type.startsWith("IBGW"))
+        if (type.startsWith("OBEP")) {
+            if (type.equals("OBEP 21"))
+                len /= 1.5;
+            else
+                len *= 1.5;
+        } else if (type.startsWith("IBGW")) {
             len /= 1.5;
+        }
         // drop in proportion to size w.r.t. a standard 1024-byte message
         // this is a little expensive but we want to adjust the curve between 0 and 1
         // Most messages are 1024, only at the OBEP do we see other sizes
diff --git a/router/java/src/net/i2p/router/tunnel/TunnelGateway.java b/router/java/src/net/i2p/router/tunnel/TunnelGateway.java
index 40c935e795de58a985b6aa02ea3d8935dc35e72f..2b394e6dae5a1d7bb8844b68a681de0ed2568107 100644
--- a/router/java/src/net/i2p/router/tunnel/TunnelGateway.java
+++ b/router/java/src/net/i2p/router/tunnel/TunnelGateway.java
@@ -154,12 +154,18 @@ public class TunnelGateway {
         
     public interface QueuePreprocessor {
         /** 
+         * Caller must synchronize on the list!
+         *
          * @param pending list of Pending objects for messages either unsent
          *                or partly sent.  This list should be update with any
          *                values removed (the preprocessor owns the lock)
+         *                Messages are not removed from the list until actually sent.
+         *                The status of unsent and partially-sent messages is stored in
+         *                the Pending structure.
+         *
          * @return true if we should delay before preprocessing again 
          */
-        public boolean preprocessQueue(List pending, Sender sender, Receiver receiver);
+        public boolean preprocessQueue(List<Pending> pending, Sender sender, Receiver receiver);
         
         /** how long do we want to wait before flushing */
         public long getDelayAmount();
@@ -173,6 +179,9 @@ public class TunnelGateway {
         public long receiveEncrypted(byte encrypted[]);
     }
     
+    /**
+     *  Stores all the state for an unsent or partially-sent message
+     */
     public static class Pending {
         protected Hash _toRouter;
         protected TunnelId _toTunnel;
@@ -182,7 +191,7 @@ public class TunnelGateway {
         protected int _offset;
         protected int _fragmentNumber;
         protected long _created;
-        private List _messageIds;
+        private List<Long> _messageIds;
         
         public Pending(I2NPMessage message, Hash toRouter, TunnelId toTunnel) { 
             this(message, toRouter, toTunnel, System.currentTimeMillis()); 
@@ -222,7 +231,7 @@ public class TunnelGateway {
                 _messageIds.add(new Long(id));
             }
         }
-        public List getMessageIds() { 
+        public List<Long> getMessageIds() { 
             synchronized (Pending.this) { 
                 if (_messageIds != null)
                     return new ArrayList(_messageIds); 
@@ -231,6 +240,8 @@ public class TunnelGateway {
             } 
         }
     }
+
+    /** Extend for debugging */
     class PendingImpl extends Pending {
         public PendingImpl(I2NPMessage message, Hash toRouter, TunnelId toTunnel) {
             super(message, toRouter, toTunnel, _context.clock().now());
diff --git a/router/java/src/net/i2p/router/tunnel/TunnelParticipant.java b/router/java/src/net/i2p/router/tunnel/TunnelParticipant.java
index a73208e3bb49a1060bd2c967f1ac668befed4667..6263f8ec701b98db1102e21d7e5c347295674cbe 100644
--- a/router/java/src/net/i2p/router/tunnel/TunnelParticipant.java
+++ b/router/java/src/net/i2p/router/tunnel/TunnelParticipant.java
@@ -101,8 +101,11 @@ public class TunnelParticipant {
         }
     }
     
+/****
     private int _periodMessagesTransferred;
     private long _lastCoallesced = System.currentTimeMillis();
+****/
+
     /** 
      * take note that the peers specified were able to push us data.  hmm, is this safe?
      * this could be easily gamed to get us to rank some peer of their choosing as quite
diff --git a/router/java/src/net/i2p/router/tunnel/pool/BuildExecutor.java b/router/java/src/net/i2p/router/tunnel/pool/BuildExecutor.java
index e1cec3607c3ac206dd33ebb1e9d6d01b1afde3dc..5e4456490f201bf51ef2e2f7b1aea631243ee0de 100644
--- a/router/java/src/net/i2p/router/tunnel/pool/BuildExecutor.java
+++ b/router/java/src/net/i2p/router/tunnel/pool/BuildExecutor.java
@@ -54,9 +54,7 @@ class BuildExecutor implements Runnable {
 
         // Get stat manager, get recognized bandwidth tiers
         StatManager statMgr = _context.statManager();
-        @SuppressWarnings("static-access")
-        /* FIXME Accessing static field "BW_CAPABILITY_CHARS" FIXME */
-        String bwTiers = _context.router().getRouterInfo().BW_CAPABILITY_CHARS;
+        String bwTiers = RouterInfo.BW_CAPABILITY_CHARS;
         // For each bandwidth tier, create tunnel build agree/reject/expire stats
         for (int i = 0; i < bwTiers.length(); i++) {
             String bwTier = String.valueOf(bwTiers.charAt(i));
diff --git a/router/java/src/net/i2p/router/tunnel/pool/PooledTunnelCreatorConfig.java b/router/java/src/net/i2p/router/tunnel/pool/PooledTunnelCreatorConfig.java
index b2cb7c704fc7232225e08ecc026b1b087f1bb930..dfbdd3ecaccab23e5588b1c58ac5dd9183c565fe 100644
--- a/router/java/src/net/i2p/router/tunnel/pool/PooledTunnelCreatorConfig.java
+++ b/router/java/src/net/i2p/router/tunnel/pool/PooledTunnelCreatorConfig.java
@@ -79,8 +79,9 @@ public class PooledTunnelCreatorConfig extends TunnelCreatorConfig {
     public TunnelPool getTunnelPool() { return _pool; }
     
 
-    /* FIXME Exporting non-public type through public API FIXME */
-    public void setTestJob(TestJob job) { _testJob = job; }
+    /** @deprecated unused, which makes _testJob unused - why is it here */
+    void setTestJob(TestJob job) { _testJob = job; }
+    /** does nothing, to be deprecated */
     public void setExpireJob(Job job) { /* _expireJob = job; */ }
     
     /**
diff --git a/router/java/src/net/i2p/router/tunnel/pool/TunnelPoolManager.java b/router/java/src/net/i2p/router/tunnel/pool/TunnelPoolManager.java
index 1eae1de451b8ab883b0f9c6aac8ee56c629a4a0a..bbee14a4840ca9e7175025e0739a7d787052086b 100644
--- a/router/java/src/net/i2p/router/tunnel/pool/TunnelPoolManager.java
+++ b/router/java/src/net/i2p/router/tunnel/pool/TunnelPoolManager.java
@@ -29,6 +29,8 @@ import net.i2p.stat.RateStat;
 import net.i2p.util.I2PThread;
 import net.i2p.util.Log;
 import net.i2p.util.ObjectCounter;
+import net.i2p.util.SimpleScheduler;
+import net.i2p.util.SimpleTimer;
 
 /**
  * 
@@ -42,7 +44,7 @@ public class TunnelPoolManager implements TunnelManagerFacade {
     private final Map<Hash, TunnelPool> _clientOutboundPools;
     private TunnelPool _inboundExploratory;
     private TunnelPool _outboundExploratory;
-    private BuildExecutor _executor;
+    private final BuildExecutor _executor;
     private boolean _isShutdown;
     
     public TunnelPoolManager(RouterContext ctx) {
@@ -263,6 +265,7 @@ public class TunnelPoolManager implements TunnelManagerFacade {
         TunnelPool inbound = null;
         TunnelPool outbound = null;
         // should we share the clientPeerSelector across both inbound and outbound?
+        // or just one for all clients? why separate?
         synchronized (_clientInboundPools) {
             inbound = _clientInboundPools.get(dest);
             if (inbound == null) {
@@ -284,11 +287,22 @@ public class TunnelPoolManager implements TunnelManagerFacade {
             }
         }
         inbound.startup();
-        try { Thread.sleep(3*1000); } catch (InterruptedException ie) {}
-        outbound.startup();
+        SimpleScheduler.getInstance().addEvent(new DelayedStartup(outbound), 3*1000);
     }
     
     
+    private static class DelayedStartup implements SimpleTimer.TimedEvent {
+        private TunnelPool pool;
+
+        public DelayedStartup(TunnelPool p) {
+            this.pool = p;
+        }
+
+        public void timeReached() {
+            this.pool.startup();
+        }
+    }
+
     public void removeTunnels(Hash destination) {
         if (destination == null) return;
         if (_context.clientManager().isLocal(destination)) {
@@ -361,12 +375,11 @@ public class TunnelPoolManager implements TunnelManagerFacade {
         _inboundExploratory = new TunnelPool(_context, this, inboundSettings, selector);
         _inboundExploratory.startup();
         
-        try { Thread.sleep(3*1000); } catch (InterruptedException ie) {}
         TunnelPoolSettings outboundSettings = new TunnelPoolSettings();
         outboundSettings.setIsExploratory(true);
         outboundSettings.setIsInbound(false);
         _outboundExploratory = new TunnelPool(_context, this, outboundSettings, selector);
-        _outboundExploratory.startup();
+        SimpleScheduler.getInstance().addEvent(new DelayedStartup(_outboundExploratory), 3*1000);
         
         // try to build up longer tunnels
         _context.jobQueue().addJob(new BootstrapPool(_context, _inboundExploratory));