diff --git a/core/java/build.xml b/core/java/build.xml
index ad7597559fd9efb4794fc1fa4f05f8c954673675..2038fd505c98848e61d34081373ce9e145c90709 100644
--- a/core/java/build.xml
+++ b/core/java/build.xml
@@ -223,7 +223,11 @@
         <!-- warning - The junit.test target below doesn't actually include i2ptest.jar in the classpath,
              only the build/obj directory.
           -->
-        <jar destfile="./build/i2ptest.jar" basedir="./build/obj" includes="**/*.class **/test.data **/baseDblPrecmp **/basePrecmp" />
+        <jar destfile="./build/i2ptest.jar" basedir="./build/obj" >
+            <fileset dir="./build/obj" includes="**/*.class **/test.data **/baseDblPrecmp **/basePrecmp" />
+            <!-- the getopt translation files -->
+            <fileset dir="src" includes="${translation.includes}" />
+        </jar>
     </target>
     <!-- preparation of code coverage tool of choice -->
     <target name="prepareClover" depends="compile" if="with.clover">
diff --git a/router/java/test/junit/net/i2p/router/client/LocalClientManager.java b/router/java/test/junit/net/i2p/router/client/LocalClientManager.java
index 0f9ba30aba09a57458f966b4046262ce7d8347c5..2d000317960e56acaa7eebcfbf3cc17bd94a0f8a 100644
--- a/router/java/test/junit/net/i2p/router/client/LocalClientManager.java
+++ b/router/java/test/junit/net/i2p/router/client/LocalClientManager.java
@@ -8,12 +8,15 @@ package net.i2p.router.client;
  *
  */
 
+import gnu.getopt.Getopt;
+
 import net.i2p.data.Destination;
 import net.i2p.data.Payload;
 import net.i2p.data.i2cp.MessageId;
 import net.i2p.data.i2cp.MessageStatusMessage;
 import net.i2p.router.RouterContext;
 import net.i2p.util.I2PThread;
+import net.i2p.util.SimpleTimer2;
 
 /**
  * For testing clients without a full router.
@@ -25,6 +28,7 @@ import net.i2p.util.I2PThread;
  * @since 0.9.8
  */
 class LocalClientManager extends ClientManager {
+    private static int dropX1000 = 0, jitter = 0, latency = 0;
 
     /**
      *  @param context stub, may be constructed with new RouterContext(null),
@@ -45,7 +49,7 @@ class LocalClientManager extends ClientManager {
 
     /**
      * Local only
-     * TODO: add simulated delay and random drops to test streaming.
+     * TODO: we could have per-destination delay/drop parameters in the client options
      *
      * @param flags ignored for local
      */
@@ -56,9 +60,31 @@ class LocalClientManager extends ClientManager {
         ClientConnectionRunner sender = getRunner(fromDest);
         ClientConnectionRunner runner = getRunner(toDest);
         if (runner != null) {
-            runner.receiveMessage(toDest, fromDest, payload);
-            if (sender != null)
-                sender.updateMessageDeliveryStatus(fromDest, msgId, messageNonce, MessageStatusMessage.STATUS_SEND_SUCCESS_LOCAL);
+            if (dropX1000 > 0) {
+                if (100 * 1000 * _ctx.random().nextFloat() < dropX1000) {
+                    System.out.println("Message " + msgId + " DROPPED randomly");
+                    if (sender != null) {
+                        // pretend success
+                        sender.updateMessageDeliveryStatus(fromDest, msgId, messageNonce, MessageStatusMessage.STATUS_SEND_GUARANTEED_SUCCESS);
+                    }
+                }
+            }
+            if (latency > 0 || jitter > 0) {
+                int delay = latency;
+                if (jitter > 0)
+                    delay += (int) (jitter * _ctx.random().nextGaussian());
+                if (delay > 0) {
+                    System.out.println("Message " + msgId + " DELAYED " + delay + " ms");
+                    DelayedSend ds = new DelayedSend(_ctx, sender, runner, fromDest, toDest, payload, msgId, messageNonce);
+                    ds.schedule(delay);
+                    return;
+                }
+            }
+            boolean ok = runner.receiveMessage(toDest, fromDest, payload);
+            if (sender != null) {
+                int rc = ok ? MessageStatusMessage.STATUS_SEND_SUCCESS_LOCAL : MessageStatusMessage.STATUS_SEND_FAILURE_LOCAL;
+                sender.updateMessageDeliveryStatus(fromDest, msgId, messageNonce, rc);
+            }
         } else {
             // remote.  ignore.
             System.out.println("Message " + msgId + " is targeting a REMOTE destination - DROPPED");
@@ -67,13 +93,87 @@ class LocalClientManager extends ClientManager {
         }
     }
 
+    private static class DelayedSend extends SimpleTimer2.TimedEvent {
+        private final ClientConnectionRunner s, r;
+        private final Destination fd, td;
+        private final Payload pl;
+        private final MessageId id;
+        private final long nonce;
+
+        public DelayedSend(RouterContext ctx, ClientConnectionRunner sender, ClientConnectionRunner runner,
+                           Destination fromDest, Destination toDest, Payload payload,
+                           MessageId msgId, long messageNonce) {
+            super(ctx.simpleTimer2());
+            s = sender; r = runner; fd = fromDest;
+            td = toDest; pl = payload;
+            id = msgId; nonce = messageNonce;
+        }
+
+        public void timeReached() {
+            boolean ok = r.receiveMessage(td, fd, pl);
+            if (s != null) {
+                int rc = ok ? MessageStatusMessage.STATUS_SEND_SUCCESS_LOCAL : MessageStatusMessage.STATUS_SEND_FAILURE_LOCAL;
+                s.updateMessageDeliveryStatus(fd, id, nonce, rc);
+            }
+        }
+    }
+
     public static void main(String args[]) {
+        int dropX1000 = 0, jitter = 0, latency = 0;
+        boolean error = false;
+        Getopt g = new Getopt("router", args, "d:j:l:");
+        try {
+            int c;
+            while ((c = g.getopt()) != -1) {
+                switch (c) {
+
+                    case 'd':
+                        dropX1000 = (int) (1000 * Double.parseDouble(g.getOptarg()));
+                        if (dropX1000 < 0 || dropX1000 >= 100 * 1000)
+                            error = true;
+                        break;
+
+                    case 'j':
+                        jitter = Integer.parseInt(g.getOptarg());
+                        if (jitter < 0)
+                            error = true;
+                        break;
+
+                    case 'l':
+                        latency = Integer.parseInt(g.getOptarg());
+                        if (latency < 0)
+                            error = true;
+                        break;
+
+                    default:
+                        error = true;
+                }
+            }
+        } catch (RuntimeException e) {
+            e.printStackTrace();
+            error = true;
+        }
+        if (error || args.length - g.getOptind() > 0) {
+            usage();
+            System.exit(1);
+        }
+
         RouterContext ctx = new RouterContext(null);
         int port = ClientManagerFacadeImpl.DEFAULT_PORT;
-        ClientManager mgr = new LocalClientManager(ctx, port);
+        LocalClientManager mgr = new LocalClientManager(ctx, port);
+        mgr.dropX1000 = dropX1000;
+        mgr.jitter = jitter;
+        mgr.latency = latency;
         mgr.start();
         System.out.println("Listening on port " + port);
         try { Thread.sleep(60*60*1000); } catch (InterruptedException ie) {}
         System.out.println("Done listening on port " + port);
     }
+
+    private static void usage() {
+        System.err.println("usage: LocalClientManager\n" +
+                           "         [-d droppercent] // 0.0 - 99.99999 (default 0)\n" +
+                           "         [-j jitter]      // (integer ms for 1 std. deviation, default 0)\n" +
+                           "         [-l latency]     // (integer ms, default 0)");
+    }
 }