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()); }