diff --git a/core/java/src/net/i2p/client/I2PSession.java b/core/java/src/net/i2p/client/I2PSession.java
index 567c9e521dbfc5981c2e67407a38d35452ed40b6..d21c858d71423f4a7147c2bbf8028948b36c8ab8 100644
--- a/core/java/src/net/i2p/client/I2PSession.java
+++ b/core/java/src/net/i2p/client/I2PSession.java
@@ -130,14 +130,19 @@ public interface I2PSession {
 
     /** Receive a message that the router has notified the client about, returning
      * the payload.
+     * This may only be called once for a given msgId (until the counter wraps)
+     *
      * @param msgId message to fetch
-     * @return unencrypted body of the message
+     * @return unencrypted body of the message, or null if not found
      */
     public byte[] receiveMessage(int msgId) throws I2PSessionException;
 
     /** Instruct the router that the message received was abusive (including how
      * abusive on a 1-100 scale) in the hopes the router can do something to
      * minimize receiving abusive messages like that in the future.
+     *
+     * Unused. Not fully implemented.
+     *
      * @param msgId message that was abusive (or -1 for not message related)
      * @param severity how abusive
      */
diff --git a/core/java/src/net/i2p/client/I2PSessionImpl.java b/core/java/src/net/i2p/client/I2PSessionImpl.java
index dc7e875d9cf4f6dd0798fbbb0bfb955b5244f4f5..0a29ff9d81196fac8dac414166767dabd7d937b4 100644
--- a/core/java/src/net/i2p/client/I2PSessionImpl.java
+++ b/core/java/src/net/i2p/client/I2PSessionImpl.java
@@ -25,6 +25,7 @@ import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.LinkedBlockingQueue;
 
+import net.i2p.CoreVersion;
 import net.i2p.I2PAppContext;
 import net.i2p.data.DataFormatException;
 import net.i2p.data.Destination;
@@ -359,7 +360,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
             if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "before startReading");
             _reader.startReading();
             if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "Before getDate");
-            sendMessage(new GetDateMessage());
+            sendMessage(new GetDateMessage(CoreVersion.VERSION));
             if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "After getDate / begin waiting for a response");
             int waitcount = 0;
             while (!_dateReceived) {
diff --git a/core/java/src/net/i2p/client/I2PSessionListener.java b/core/java/src/net/i2p/client/I2PSessionListener.java
index a76c6ab0f0818d7a357e9583763a42dad3c3ed39..6db1da3675571f2da940a35433eb0514127f1f48 100644
--- a/core/java/src/net/i2p/client/I2PSessionListener.java
+++ b/core/java/src/net/i2p/client/I2PSessionListener.java
@@ -13,11 +13,25 @@ package net.i2p.client;
  * Define a means for the router to asynchronously notify the client that a
  * new message is available or the router is under attack.
  *
+ * A client must implement and register this via addSessionListener()
+ * to receive messages.
+ *
+ * If you wish to get notification of the protocol, from port, and to port,
+ * or wish to get notification of certain protocols and ports only,
+ * you must use I2PSessionMuxedListener and addMuxedSessionListener() instead.
+ *
  * @author jrandom
  */
 public interface I2PSessionListener {
     /** Instruct the client that the given session has received a message with
      * size # of bytes.
+     *
+     * After this is called, the client should call receiveMessage(msgId).
+     * There is currently no method for the client to reject the message.
+     * If the client does not call receiveMessage() within a timeout period
+     * (currently 30 seconds), the session will delete the message and
+     * log an error.
+     *
      * @param session session to notify
      * @param msgId message number available
      * @param size size of the message - why it's a long and not an int is a mystery
@@ -26,6 +40,9 @@ public interface I2PSessionListener {
 
     /** Instruct the client that the session specified seems to be under attack
      * and that the client may wish to move its destination to another router.
+     *
+     * Unused. Not fully implemented.
+     *
      * @param session session to report abuse to
      * @param severity how bad the abuse is
      */
@@ -39,6 +56,7 @@ public interface I2PSessionListener {
 
     /**
      * Notify the client that some error occurred
+     *
      * @param error can be null? or not?
      */
     void errorOccurred(I2PSession session, String message, Throwable error);
diff --git a/core/java/src/net/i2p/client/I2PSessionMuxedListener.java b/core/java/src/net/i2p/client/I2PSessionMuxedListener.java
index 118dc75caea9011239273448d7cc5f65266a4387..93abb79745cfe932fe0771969d60b502a1767443 100644
--- a/core/java/src/net/i2p/client/I2PSessionMuxedListener.java
+++ b/core/java/src/net/i2p/client/I2PSessionMuxedListener.java
@@ -20,6 +20,12 @@ public interface I2PSessionMuxedListener extends I2PSessionListener {
      * If you register via addSessionListener(),
      * this will be called only for the proto(s) and toport(s) you register for.
      *
+     * After this is called, the client should call receiveMessage(msgId).
+     * There is currently no method for the client to reject the message.
+     * If the client does not call receiveMessage() within a timeout period
+     * (currently 30 seconds), the session will delete the message and
+     * log an error.
+     *
      * @param session session to notify
      * @param msgId message number available
      * @param size size of the message - why it's a long and not an int is a mystery
@@ -32,6 +38,15 @@ public interface I2PSessionMuxedListener extends I2PSessionListener {
      * Will be called only if you register via addMuxedSessionListener().
      * Will be called only for the proto(s) and toport(s) you register for.
      *
+     * After this is called, the client should call receiveMessage(msgId).
+     * There is currently no method for the client to reject the message.
+     * If the client does not call receiveMessage() within a timeout period
+     * (currently 30 seconds), the session will delete the message and
+     * log an error.
+     *
+     * Only one listener is called for a given message, even if more than one
+     * have registered. See I2PSessionDemultiplexer for details.
+     *
      * @param session session to notify
      * @param msgId message number available
      * @param size size of the message - why it's a long and not an int is a mystery
@@ -43,20 +58,27 @@ public interface I2PSessionMuxedListener extends I2PSessionListener {
 
     /** Instruct the client that the session specified seems to be under attack
      * and that the client may wish to move its destination to another router.
+     * All registered listeners will be called.
+     *
+     * Unused. Not fully implemented.
+     *
      * @param session session to report abuse to
      * @param severity how bad the abuse is
      */
     void reportAbuse(I2PSession session, int severity);
 
     /**
-     * Notify the client that the session has been terminated
+     * Notify the client that the session has been terminated.
+     * All registered listeners will be called.
      *
      */
     void disconnected(I2PSession session);
 
     /**
-     * Notify the client that some error occurred
+     * Notify the client that some error occurred.
+     * All registered listeners will be called.
      *
+     * @param error can be null? or not?
      */
     void errorOccurred(I2PSession session, String message, Throwable error);
 }
diff --git a/core/java/src/net/i2p/client/SetDateMessageHandler.java b/core/java/src/net/i2p/client/SetDateMessageHandler.java
index bddc36d669c2a1887b73437629375351634095e0..3f0e6dc6415a6391b21b59f67c54f4d5c3c13fa1 100644
--- a/core/java/src/net/i2p/client/SetDateMessageHandler.java
+++ b/core/java/src/net/i2p/client/SetDateMessageHandler.java
@@ -34,6 +34,7 @@ class SetDateMessageHandler extends HandlerImpl {
         // we did was get the time from ourselves.
         if (!_context.isRouterContext())
             Clock.getInstance().setNow(msg.getDate().getTime());
+        // TODO - save router's version string for future reference
         session.dateUpdated();
     }
 }
diff --git a/core/java/src/net/i2p/data/i2cp/GetDateMessage.java b/core/java/src/net/i2p/data/i2cp/GetDateMessage.java
index f36ec02d921d112d46155cbc602ef0f45937de83..b5b8367517a8664a810ca2aaa041e4c74f434c7e 100644
--- a/core/java/src/net/i2p/data/i2cp/GetDateMessage.java
+++ b/core/java/src/net/i2p/data/i2cp/GetDateMessage.java
@@ -9,40 +9,81 @@ package net.i2p.data.i2cp;
  *
  */
 
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 
+import net.i2p.data.DataFormatException;
+import net.i2p.data.DataHelper;
+
 /**
- * Request the other side to send us what they think the current time is
+ * Request the other side to send us what they think the current time is/
+ * Only supported from client to router.
  *
+ * Since 0.8.7, optionally include a version string.
  */
 public class GetDateMessage extends I2CPMessageImpl {
     public final static int MESSAGE_TYPE = 32;
+    private String _version;
 
     public GetDateMessage() {
         super();
     }
 
+    /**
+     *  @param version the client's version String to be sent to the router; may be null
+     *  @since 0.8.7
+     */
+    public GetDateMessage(String version) {
+        super();
+        _version = version;
+    }
+
+    /**
+     *  @return may be null
+     *  @since 0.8.7
+     */
+    public String getVersion() {
+        return _version;
+    }
+
     @Override
     protected void doReadMessage(InputStream in, int size) throws I2CPMessageException, IOException {
-        // noop
+        if (size > 0) {
+            try {
+                _version = DataHelper.readString(in);
+            } catch (DataFormatException dfe) {
+                throw new I2CPMessageException("Bad version string", dfe);
+            }
+        }
     }
 
     @Override
     protected byte[] doWriteMessage() throws I2CPMessageException, IOException {
-        byte rv[] = new byte[0];
-        return rv;
+        if (_version == null)
+            return new byte[0];
+        ByteArrayOutputStream os = new ByteArrayOutputStream(16);
+        try {
+            DataHelper.writeString(os, _version);
+        } catch (DataFormatException dfe) {
+            throw new I2CPMessageException("Error writing out the message data", dfe);
+        }
+        return os.toByteArray();
     }
 
     public int getType() {
         return MESSAGE_TYPE;
     }
 
-    /* FIXME missing hashCode() method FIXME */
+    @Override
+    public int hashCode() {
+        return MESSAGE_TYPE ^ DataHelper.hashCode(_version);
+    }
+
     @Override
     public boolean equals(Object object) {
         if ((object != null) && (object instanceof GetDateMessage)) {
-            return true;
+            return DataHelper.eq(_version, ((GetDateMessage)object)._version);
         }
         
         return false;
@@ -52,6 +93,7 @@ public class GetDateMessage extends I2CPMessageImpl {
     public String toString() {
         StringBuilder buf = new StringBuilder();
         buf.append("[GetDateMessage]");
+        buf.append("\n\tVersion: ").append(_version);
         return buf.toString();
     }
 }
diff --git a/core/java/src/net/i2p/data/i2cp/SetDateMessage.java b/core/java/src/net/i2p/data/i2cp/SetDateMessage.java
index 9f6633b6ddda1f1d87b6bea65be90d52a1cae6b1..517992d394eeee7164d2d44c31efeab53d191175 100644
--- a/core/java/src/net/i2p/data/i2cp/SetDateMessage.java
+++ b/core/java/src/net/i2p/data/i2cp/SetDateMessage.java
@@ -19,18 +19,30 @@ import net.i2p.data.DataHelper;
 import net.i2p.util.Clock;
 
 /**
- * Tell the other side what time it is
+ * Tell the other side what time it is.
+ * Only supported from router to client.
  *
+ * Since 0.8.7, optionally include a version string.
  */
 public class SetDateMessage extends I2CPMessageImpl {
     public final static int MESSAGE_TYPE = 33;
     private Date _date;
+    private String _version;
 
     public SetDateMessage() {
         super();
         _date = new Date(Clock.getInstance().now());
     }
 
+    /**
+     *  @param version the router's version String to be sent to the client; may be null
+     *  @since 0.8.7
+     */
+    public SetDateMessage(String version) {
+        this();
+        _version = version;
+    }
+
     public Date getDate() {
         return _date;
     }
@@ -39,10 +51,20 @@ public class SetDateMessage extends I2CPMessageImpl {
         _date = date;
     }
 
+    /**
+     *  @return may be null
+     *  @since 0.8.7
+     */
+    public String getVersion() {
+        return _version;
+    }
+
     @Override
     protected void doReadMessage(InputStream in, int size) throws I2CPMessageException, IOException {
         try {
             _date = DataHelper.readDate(in);
+            if (size > DataHelper.DATE_LENGTH)
+                _version = DataHelper.readString(in);
         } catch (DataFormatException dfe) {
             throw new I2CPMessageException("Unable to load the message data", dfe);
         }
@@ -52,9 +74,11 @@ public class SetDateMessage extends I2CPMessageImpl {
     protected byte[] doWriteMessage() throws I2CPMessageException, IOException {
         if (_date == null)
             throw new I2CPMessageException("Unable to write out the message as there is not enough data");
-        ByteArrayOutputStream os = new ByteArrayOutputStream(64);
+        ByteArrayOutputStream os = new ByteArrayOutputStream(32);
         try {
             DataHelper.writeDate(os, _date);
+            if (_version != null)
+                DataHelper.writeString(os, _version);
         } catch (DataFormatException dfe) {
             throw new I2CPMessageException("Error writing out the message data", dfe);
         }
@@ -65,12 +89,16 @@ public class SetDateMessage extends I2CPMessageImpl {
         return MESSAGE_TYPE;
     }
 
-    /* FIXME missing hashCode() method */
+    @Override
+    public int hashCode() {
+        return MESSAGE_TYPE ^ DataHelper.hashCode(_version) ^ DataHelper.hashCode(_date);
+    }
+
     @Override
     public boolean equals(Object object) {
         if ((object != null) && (object instanceof SetDateMessage)) {
             SetDateMessage msg = (SetDateMessage) object;
-            return DataHelper.eq(_date, msg.getDate());
+            return DataHelper.eq(_date, msg._date) && DataHelper.eq(_version, msg._version);
         }
             
         return false;
@@ -81,6 +109,7 @@ public class SetDateMessage extends I2CPMessageImpl {
         StringBuilder buf = new StringBuilder();
         buf.append("[SetDateMessage");
         buf.append("\n\tDate: ").append(_date);
+        buf.append("\n\tVersion: ").append(_version);
         buf.append("]");
         return buf.toString();
     }
diff --git a/history.txt b/history.txt
index fe4744abf6340e0f2453b366eb9554e9af6e019d..784bb836ce06ce72ef1d5c1031189eabd363beef 100644
--- a/history.txt
+++ b/history.txt
@@ -1,3 +1,21 @@
+2011-05-30 zzz
+    * I2CP:
+      - Append I2CP Version information to the Get/Set Date Messages,
+        so that both the router and client are aware of the other side's version,
+        and future protocol changes will be easier to implement.
+        Previously, router version information was not available to the client,
+        so when router and client were in different JVMs,
+        old clients would work with new routers
+        but new clients would not work with old routers.
+        After this change, we can design future changes so that new clients
+        will work with old routers.
+        This is an enhancement to the old protocol version byte sent by the client,
+        which we have never changed and probably never will.
+      - Prevent a client from setting the router's clock
+      - Javadocs
+    * i2psnark: Restrict swarm size for small torrents
+    * netDb: Don't refetch expiring router infos if we have enough
+
 2011-05-28 zzz
     * i2psnark: Fix bug preventing Robert from connecting to snark (thx sponge)
 
diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java
index 5a58b19bc22ea9b489b5610aade18177efc4505e..e36cd3b14352a16a1e79c5fea474cb2f20cbaa5c 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 = 11;
+    public final static long BUILD = 12;
 
     /** for example "-test" */
     public final static String EXTRA = "";
diff --git a/router/java/src/net/i2p/router/client/ClientMessageEventListener.java b/router/java/src/net/i2p/router/client/ClientMessageEventListener.java
index d45df2cdb0245e7e779b29ec770f6832bc19da7a..653f46e2353f2b382bdc842737b11b7aea9cd74e 100644
--- a/router/java/src/net/i2p/router/client/ClientMessageEventListener.java
+++ b/router/java/src/net/i2p/router/client/ClientMessageEventListener.java
@@ -10,6 +10,7 @@ package net.i2p.router.client;
 
 import java.util.Properties;
 
+import net.i2p.CoreVersion;
 import net.i2p.data.Payload;
 import net.i2p.data.i2cp.BandwidthLimitsMessage;
 import net.i2p.data.i2cp.CreateLeaseSetMessage;
@@ -120,22 +121,30 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
         // Is this is a little drastic for an unknown message type?
         _runner.stopRunning();
     }
-    
+  
     public void disconnected(I2CPMessageReader reader) {
         if (_runner.isDead()) return;
         _runner.disconnected();
     }
     
     private void handleGetDate(I2CPMessageReader reader, GetDateMessage message) {
+        // sent by clients >= 0.8.7
+        String clientVersion = message.getVersion();
+        // TODO - save client's version string for future reference
         try {
-            _runner.doSend(new SetDateMessage());
+            // only send version if the client can handle it (0.8.7 or greater)
+            _runner.doSend(new SetDateMessage(clientVersion != null ? CoreVersion.VERSION : null));
         } catch (I2CPMessageException ime) {
             if (_log.shouldLog(Log.ERROR))
                 _log.error("Error writing out the setDate message", ime);
         }
     }
+
+    /**
+     *  As of 0.8.7, does nothing. Do not allow a client to set the router's clock.
+     */
     private void handleSetDate(I2CPMessageReader reader, SetDateMessage message) {
-        _context.clock().setNow(message.getDate().getTime());
+        //_context.clock().setNow(message.getDate().getTime());
     }