From d4bf2523a61bc31938aa82cc638440f9d84f22f8 Mon Sep 17 00:00:00 2001
From: zzz <zzz@mail.i2p>
Date: Mon, 30 May 2011 16:31:09 +0000
Subject: [PATCH]     * 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

---
 core/java/src/net/i2p/client/I2PSession.java  |  7 ++-
 .../src/net/i2p/client/I2PSessionImpl.java    |  3 +-
 .../net/i2p/client/I2PSessionListener.java    | 18 +++++++
 .../i2p/client/I2PSessionMuxedListener.java   | 26 ++++++++-
 .../net/i2p/client/SetDateMessageHandler.java |  1 +
 .../src/net/i2p/data/i2cp/GetDateMessage.java | 54 ++++++++++++++++---
 .../src/net/i2p/data/i2cp/SetDateMessage.java | 37 +++++++++++--
 history.txt                                   | 18 +++++++
 .../src/net/i2p/router/RouterVersion.java     |  2 +-
 .../client/ClientMessageEventListener.java    | 15 ++++--
 10 files changed, 163 insertions(+), 18 deletions(-)

diff --git a/core/java/src/net/i2p/client/I2PSession.java b/core/java/src/net/i2p/client/I2PSession.java
index 567c9e521..d21c858d7 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 dc7e875d9..0a29ff9d8 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 a76c6ab0f..6db1da367 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 118dc75ca..93abb7974 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 bddc36d66..3f0e6dc64 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 f36ec02d9..b5b836751 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 9f6633b6d..517992d39 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 fe4744abf..784bb836c 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 5a58b19bc..e36cd3b14 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 d45df2cdb..653f46e23 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());
     }
 	
     
-- 
GitLab