* I2CP: Client-side prep for asynch status for sent messages (ticket #788)

- Clean up and reuse MessageState for asynch notification
   - New I2PSession sendMessage() method and listener
   - Move VerifyUsage from SimpleScheduler to SimpleTimer2 for efficiency
   - Fix up javadocs
This commit is contained in:
zzz
2014-05-15 20:11:21 +00:00
parent a93666cd36
commit 8371b8f806
7 changed files with 397 additions and 422 deletions

View File

@ -301,7 +301,7 @@ class I2CPMessageProducer {
* @param key unused - no end-to-end crypto * @param key unused - no end-to-end crypto
* @param newKey unused - no end-to-end crypto * @param newKey unused - no end-to-end crypto
*/ */
private Payload createPayload(Destination dest, byte[] payload, SessionTag tag, SessionKey key, Set tags, private Payload createPayload(Destination dest, byte[] payload, SessionTag tag, SessionKey key, Set<SessionTag> tags,
SessionKey newKey) throws I2PSessionException { SessionKey newKey) throws I2PSessionException {
if (dest == null) throw new I2PSessionException("No destination specified"); if (dest == null) throw new I2PSessionException("No destination specified");
if (payload == null) throw new I2PSessionException("No payload specified"); if (payload == null) throw new I2PSessionException("No payload specified");
@ -346,8 +346,8 @@ class I2CPMessageProducer {
* to the router * to the router
* *
*/ */
public void createLeaseSet(I2PSessionImpl session, LeaseSet leaseSet, SigningPrivateKey signingPriv, PrivateKey priv) public void createLeaseSet(I2PSessionImpl session, LeaseSet leaseSet, SigningPrivateKey signingPriv,
throws I2PSessionException { PrivateKey priv) throws I2PSessionException {
CreateLeaseSetMessage msg = new CreateLeaseSetMessage(); CreateLeaseSetMessage msg = new CreateLeaseSetMessage();
msg.setLeaseSet(leaseSet); msg.setLeaseSet(leaseSet);
msg.setPrivateKey(priv); msg.setPrivateKey(priv);

View File

@ -47,10 +47,21 @@ public interface I2PSession {
*/ */
public boolean sendMessage(Destination dest, byte[] payload) throws I2PSessionException; public boolean sendMessage(Destination dest, byte[] payload) throws I2PSessionException;
/** Send a new message to the given destination, containing the specified
* payload, returning true if the router feels confident that the message
* was delivered.
*
* WARNING: It is recommended that you use a method that specifies the protocol and ports.
*
* @param dest location to send the message
* @param payload body of the message to be sent (unencrypted)
* @return success
*/
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size) throws I2PSessionException; public boolean sendMessage(Destination dest, byte[] payload, int offset, int size) throws I2PSessionException;
/** /**
* See I2PSessionMuxedImpl for proto/port details. * See I2PSessionMuxedImpl for proto/port details.
* @return success
* @since 0.7.1 * @since 0.7.1
*/ */
public boolean sendMessage(Destination dest, byte[] payload, int proto, int fromport, int toport) throws I2PSessionException; public boolean sendMessage(Destination dest, byte[] payload, int proto, int fromport, int toport) throws I2PSessionException;
@ -83,6 +94,7 @@ public interface I2PSession {
* @param tagsSent UNUSED, IGNORED. Set of tags delivered to the peer and associated with the keyUsed. This is also an output parameter - * @param tagsSent UNUSED, IGNORED. Set of tags delivered to the peer and associated with the keyUsed. This is also an output parameter -
* the contents of the set is ignored during the call, but afterwards it contains a set of SessionTag * the contents of the set is ignored during the call, but afterwards it contains a set of SessionTag
* objects that were sent along side the given keyUsed. * objects that were sent along side the given keyUsed.
* @return success
*/ */
public boolean sendMessage(Destination dest, byte[] payload, SessionKey keyUsed, Set tagsSent) throws I2PSessionException; public boolean sendMessage(Destination dest, byte[] payload, SessionKey keyUsed, Set tagsSent) throws I2PSessionException;
@ -90,6 +102,7 @@ public interface I2PSession {
* End-to-End Crypto is disabled, tags and keys are ignored. * End-to-End Crypto is disabled, tags and keys are ignored.
* @param keyUsed UNUSED, IGNORED. * @param keyUsed UNUSED, IGNORED.
* @param tagsSent UNUSED, IGNORED. * @param tagsSent UNUSED, IGNORED.
* @return success
*/ */
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent) throws I2PSessionException; public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent) throws I2PSessionException;
@ -98,6 +111,7 @@ public interface I2PSession {
* @param keyUsed UNUSED, IGNORED. * @param keyUsed UNUSED, IGNORED.
* @param tagsSent UNUSED, IGNORED. * @param tagsSent UNUSED, IGNORED.
* @param expire absolute expiration timestamp, NOT interval from now * @param expire absolute expiration timestamp, NOT interval from now
* @return success
* @since 0.7.1 * @since 0.7.1
*/ */
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent, long expire) throws I2PSessionException; public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent, long expire) throws I2PSessionException;
@ -107,6 +121,14 @@ public interface I2PSession {
* End-to-End Crypto is disabled, tags and keys are ignored. * End-to-End Crypto is disabled, tags and keys are ignored.
* @param keyUsed UNUSED, IGNORED. * @param keyUsed UNUSED, IGNORED.
* @param tagsSent UNUSED, IGNORED. * @param tagsSent UNUSED, IGNORED.
* @param proto 1-254 or 0 for unset; recommended:
* I2PSession.PROTO_UNSPECIFIED
* I2PSession.PROTO_STREAMING
* I2PSession.PROTO_DATAGRAM
* 255 disallowed
* @param fromPort 1-65535 or 0 for unset
* @param toPort 1-65535 or 0 for unset
* @return success
* @since 0.7.1 * @since 0.7.1
*/ */
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent, public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent,
@ -118,6 +140,14 @@ public interface I2PSession {
* @param keyUsed UNUSED, IGNORED. * @param keyUsed UNUSED, IGNORED.
* @param tagsSent UNUSED, IGNORED. * @param tagsSent UNUSED, IGNORED.
* @param expire absolute expiration timestamp, NOT interval from now * @param expire absolute expiration timestamp, NOT interval from now
* @param proto 1-254 or 0 for unset; recommended:
* I2PSession.PROTO_UNSPECIFIED
* I2PSession.PROTO_STREAMING
* I2PSession.PROTO_DATAGRAM
* 255 disallowed
* @param fromPort 1-65535 or 0 for unset
* @param toPort 1-65535 or 0 for unset
* @return success
* @since 0.7.1 * @since 0.7.1
*/ */
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent, long expire, public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent, long expire,
@ -129,6 +159,14 @@ public interface I2PSession {
* @param keyUsed UNUSED, IGNORED. * @param keyUsed UNUSED, IGNORED.
* @param tagsSent UNUSED, IGNORED. * @param tagsSent UNUSED, IGNORED.
* @param expire absolute expiration timestamp, NOT interval from now * @param expire absolute expiration timestamp, NOT interval from now
* @param proto 1-254 or 0 for unset; recommended:
* I2PSession.PROTO_UNSPECIFIED
* I2PSession.PROTO_STREAMING
* I2PSession.PROTO_DATAGRAM
* 255 disallowed
* @param fromPort 1-65535 or 0 for unset
* @param toPort 1-65535 or 0 for unset
* @return success
* @since 0.8.4 * @since 0.8.4
*/ */
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent, long expire, public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent, long expire,
@ -137,11 +175,45 @@ public interface I2PSession {
/** /**
* See I2PSessionMuxedImpl for proto/port details. * See I2PSessionMuxedImpl for proto/port details.
* See SendMessageOptions for option details. * See SendMessageOptions for option details.
*
* @param proto 1-254 or 0 for unset; recommended:
* I2PSession.PROTO_UNSPECIFIED
* I2PSession.PROTO_STREAMING
* I2PSession.PROTO_DATAGRAM
* 255 disallowed
* @param fromPort 1-65535 or 0 for unset
* @param toPort 1-65535 or 0 for unset
* @param options to be passed to the router
* @return success
* @since 0.9.2 * @since 0.9.2
*/ */
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, public boolean sendMessage(Destination dest, byte[] payload, int offset, int size,
int proto, int fromport, int toport, SendMessageOptions options) throws I2PSessionException; int proto, int fromport, int toport, SendMessageOptions options) throws I2PSessionException;
/**
* Send a message and request an asynchronous notification of delivery status.
* Notifications will be delivered at least up to the expiration specified in the options,
* or 60 seconds if not specified.
*
* See I2PSessionMuxedImpl for proto/port details.
* See SendMessageOptions for option details.
*
* @param proto 1-254 or 0 for unset; recommended:
* I2PSession.PROTO_UNSPECIFIED
* I2PSession.PROTO_STREAMING
* I2PSession.PROTO_DATAGRAM
* 255 disallowed
* @param fromPort 1-65535 or 0 for unset
* @param toPort 1-65535 or 0 for unset
* @param options to be passed to the router
* @return the message ID to be used for later notification to the listener
* @throws I2PSessionException on all errors
* @since 0.9.14
*/
public long sendMessage(Destination dest, byte[] payload, int offset, int size,
int proto, int fromport, int toport,
SendMessageOptions options, SendMessageStatusListener listener) throws I2PSessionException;
/** Receive a message that the router has notified the client about, returning /** Receive a message that the router has notified the client about, returning
* the payload. * the payload.
* This may only be called once for a given msgId (until the counter wraps) * This may only be called once for a given msgId (until the counter wraps)

View File

@ -51,7 +51,7 @@ import net.i2p.util.I2PSSLSocketFactory;
import net.i2p.util.LHMCache; import net.i2p.util.LHMCache;
import net.i2p.util.Log; import net.i2p.util.Log;
import net.i2p.util.OrderedProperties; import net.i2p.util.OrderedProperties;
import net.i2p.util.SimpleTimer; import net.i2p.util.SimpleTimer2;
import net.i2p.util.VersionComparator; import net.i2p.util.VersionComparator;
/** /**
@ -618,20 +618,24 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
} }
/** /**
* Fire up a periodic task to check for unclamed messages * Fire up a periodic task to check for unclaimed messages
* @since 0.9.1 * @since 0.9.1
*/ */
private void startVerifyUsage() { protected void startVerifyUsage() {
_context.simpleScheduler().addEvent(new VerifyUsage(), VERIFY_USAGE_TIME); new VerifyUsage();
} }
/** /**
* Check for unclaimed messages, without wastefully setting a timer for each * Check for unclaimed messages, without wastefully setting a timer for each
* message. Just copy all unclaimed ones and check 30 seconds later. * message. Just copy all unclaimed ones and check some time later.
*/ */
private class VerifyUsage implements SimpleTimer.TimedEvent { private class VerifyUsage extends SimpleTimer2.TimedEvent {
private final List<Long> toCheck = new ArrayList<Long>(); private final List<Long> toCheck = new ArrayList<Long>();
public VerifyUsage() {
super(_context.simpleTimer2(), VERIFY_USAGE_TIME);
}
public void timeReached() { public void timeReached() {
if (isClosed()) if (isClosed())
return; return;
@ -641,12 +645,12 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
for (Long msgId : toCheck) { for (Long msgId : toCheck) {
MessagePayloadMessage removed = _availableMessages.remove(msgId); MessagePayloadMessage removed = _availableMessages.remove(msgId);
if (removed != null) if (removed != null)
_log.error("Message NOT removed! id=" + msgId + ": " + removed); _log.error(getPrefix() + " Client not responding? Message not processed! id=" + msgId + ": " + removed);
} }
toCheck.clear(); toCheck.clear();
} }
toCheck.addAll(_availableMessages.keySet()); toCheck.addAll(_availableMessages.keySet());
_context.simpleScheduler().addEvent(this, VERIFY_USAGE_TIME); schedule(VERIFY_USAGE_TIME);
} }
} }

View File

@ -11,11 +11,13 @@ package net.i2p.client;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import net.i2p.I2PAppContext; import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper; import net.i2p.data.DataHelper;
@ -24,6 +26,7 @@ import net.i2p.data.SessionKey;
import net.i2p.data.i2cp.MessageId; import net.i2p.data.i2cp.MessageId;
import net.i2p.data.i2cp.MessageStatusMessage; import net.i2p.data.i2cp.MessageStatusMessage;
import net.i2p.util.Log; import net.i2p.util.Log;
import net.i2p.util.SimpleTimer2;
/** /**
* Thread safe implementation of an I2P session running over TCP. * Thread safe implementation of an I2P session running over TCP.
@ -35,7 +38,8 @@ import net.i2p.util.Log;
class I2PSessionImpl2 extends I2PSessionImpl { class I2PSessionImpl2 extends I2PSessionImpl {
/** set of MessageState objects, representing all of the messages in the process of being sent */ /** set of MessageState objects, representing all of the messages in the process of being sent */
private /* FIXME final FIXME */ Set<MessageState> _sendingStates; protected final Map<Long, MessageState> _sendingStates;
protected final AtomicLong _sendMessageNonce;
/** max # seconds to wait for confirmation of the message send */ /** max # seconds to wait for confirmation of the message send */
private final static long SEND_TIMEOUT = 60 * 1000; // 60 seconds to send private final static long SEND_TIMEOUT = 60 * 1000; // 60 seconds to send
/** should we gzip each payload prior to sending it? */ /** should we gzip each payload prior to sending it? */
@ -44,12 +48,16 @@ class I2PSessionImpl2 extends I2PSessionImpl {
/** Don't expect any MSMs from the router for outbound traffic @since 0.8.1 */ /** Don't expect any MSMs from the router for outbound traffic @since 0.8.1 */
protected boolean _noEffort; protected boolean _noEffort;
private static final long REMOVE_EXPIRED_TIME = 63*1000;
/** /**
* for extension by SimpleSession (no dest) * for extension by SimpleSession (no dest)
*/ */
protected I2PSessionImpl2(I2PAppContext context, Properties options, protected I2PSessionImpl2(I2PAppContext context, Properties options,
I2PClientMessageHandlerMap handlerMap) { I2PClientMessageHandlerMap handlerMap) {
super(context, options, handlerMap); super(context, options, handlerMap);
_sendingStates = null;
_sendMessageNonce = null;
} }
/** /**
@ -63,11 +71,12 @@ class I2PSessionImpl2 extends I2PSessionImpl {
*/ */
public I2PSessionImpl2(I2PAppContext ctx, InputStream destKeyStream, Properties options) throws I2PSessionException { public I2PSessionImpl2(I2PAppContext ctx, InputStream destKeyStream, Properties options) throws I2PSessionException {
super(ctx, destKeyStream, options); super(ctx, destKeyStream, options);
_sendingStates = new HashSet<MessageState>(32); _sendingStates = new ConcurrentHashMap<Long, MessageState>(32);
_sendMessageNonce = new AtomicLong();
// default is BestEffort // default is BestEffort
_noEffort = "none".equals(getOptions().getProperty(I2PClient.PROP_RELIABILITY, "").toLowerCase(Locale.US)); _noEffort = "none".equals(getOptions().getProperty(I2PClient.PROP_RELIABILITY, "").toLowerCase(Locale.US));
ctx.statManager().createRateStat("i2cp.sendBestEffortTotalTime", "how long to do the full sendBestEffort call?", "i2cp", new long[] { 10*60*1000 } ); //ctx.statManager().createRateStat("i2cp.sendBestEffortTotalTime", "how long to do the full sendBestEffort call?", "i2cp", new long[] { 10*60*1000 } );
//ctx.statManager().createRateStat("i2cp.sendBestEffortStage0", "first part of sendBestEffort?", "i2cp", new long[] { 10*60*1000 } ); //ctx.statManager().createRateStat("i2cp.sendBestEffortStage0", "first part of sendBestEffort?", "i2cp", new long[] { 10*60*1000 } );
//ctx.statManager().createRateStat("i2cp.sendBestEffortStage1", "second part of sendBestEffort?", "i2cp", new long[] { 10*60*1000 } ); //ctx.statManager().createRateStat("i2cp.sendBestEffortStage1", "second part of sendBestEffort?", "i2cp", new long[] { 10*60*1000 } );
//ctx.statManager().createRateStat("i2cp.sendBestEffortStage2", "third part of sendBestEffort?", "i2cp", new long[] { 10*60*1000 } ); //ctx.statManager().createRateStat("i2cp.sendBestEffortStage2", "third part of sendBestEffort?", "i2cp", new long[] { 10*60*1000 } );
@ -80,11 +89,48 @@ class I2PSessionImpl2 extends I2PSessionImpl {
//_context.statManager().createRateStat("i2cp.receiveStatusTime.3", "How long it took to get status=3 back", "i2cp", new long[] { 60*1000, 10*60*1000 }); //_context.statManager().createRateStat("i2cp.receiveStatusTime.3", "How long it took to get status=3 back", "i2cp", new long[] { 60*1000, 10*60*1000 });
_context.statManager().createRateStat("i2cp.receiveStatusTime.4", "How long it took to get status=4 back", "i2cp", new long[] { 10*60*1000 }); _context.statManager().createRateStat("i2cp.receiveStatusTime.4", "How long it took to get status=4 back", "i2cp", new long[] { 10*60*1000 });
_context.statManager().createRateStat("i2cp.receiveStatusTime.5", "How long it took to get status=5 back", "i2cp", new long[] { 10*60*1000 }); _context.statManager().createRateStat("i2cp.receiveStatusTime.5", "How long it took to get status=5 back", "i2cp", new long[] { 10*60*1000 });
_context.statManager().createRateStat("i2cp.receiveStatusTime", "How long it took to get any status", "i2cp", new long[] { 10*60*1000 }); //_context.statManager().createRateStat("i2cp.receiveStatusTime", "How long it took to get any status", "i2cp", new long[] { 10*60*1000 });
_context.statManager().createRateStat("i2cp.tx.msgCompressed", "compressed size transferred", "i2cp", new long[] { 30*60*1000 }); _context.statManager().createRateStat("i2cp.tx.msgCompressed", "compressed size transferred", "i2cp", new long[] { 30*60*1000 });
_context.statManager().createRateStat("i2cp.tx.msgExpanded", "size before compression", "i2cp", new long[] { 30*60*1000 }); _context.statManager().createRateStat("i2cp.tx.msgExpanded", "size before compression", "i2cp", new long[] { 30*60*1000 });
} }
/**
* Fire up a periodic task to check for unclaimed messages
* @since 0.9.14
*/
@Override
protected void startVerifyUsage() {
super.startVerifyUsage();
new RemoveExpired();
}
/**
* Check for expired message states, without wastefully setting a timer for each
* message.
* @since 0.9.14
*/
private class RemoveExpired extends SimpleTimer2.TimedEvent {
public RemoveExpired() {
super(_context.simpleTimer2(), REMOVE_EXPIRED_TIME);
}
public void timeReached() {
if (isClosed())
return;
if (!_sendingStates.isEmpty()) {
long now = _context.clock().now();
for (Iterator<MessageState> iter = _sendingStates.values().iterator(); iter.hasNext(); ) {
MessageState state = iter.next();
if (state.getExpires() < now)
iter.remove();
}
}
schedule(REMOVE_EXPIRED_TIME);
}
}
protected long getTimeout() { protected long getTimeout() {
return SEND_TIMEOUT; return SEND_TIMEOUT;
} }
@ -109,6 +155,7 @@ class I2PSessionImpl2 extends I2PSessionImpl {
* Todo: don't compress if destination is local? * Todo: don't compress if destination is local?
*/ */
private static final int DONT_COMPRESS_SIZE = 66; private static final int DONT_COMPRESS_SIZE = 66;
protected boolean shouldCompress(int size) { protected boolean shouldCompress(int size) {
if (size <= DONT_COMPRESS_SIZE) if (size <= DONT_COMPRESS_SIZE)
return false; return false;
@ -118,33 +165,47 @@ class I2PSessionImpl2 extends I2PSessionImpl {
return SHOULD_COMPRESS; return SHOULD_COMPRESS;
} }
/** @throws UnsupportedOperationException always, use MuxedImpl */
public void addSessionListener(I2PSessionListener lsnr, int proto, int port) { public void addSessionListener(I2PSessionListener lsnr, int proto, int port) {
throw new IllegalArgumentException("Use MuxedImpl"); throw new UnsupportedOperationException("Use MuxedImpl");
} }
/** @throws UnsupportedOperationException always, use MuxedImpl */
public void addMuxedSessionListener(I2PSessionMuxedListener l, int proto, int port) { public void addMuxedSessionListener(I2PSessionMuxedListener l, int proto, int port) {
throw new IllegalArgumentException("Use MuxedImpl"); throw new UnsupportedOperationException("Use MuxedImpl");
} }
/** @throws UnsupportedOperationException always, use MuxedImpl */
public void removeListener(int proto, int port) { public void removeListener(int proto, int port) {
throw new IllegalArgumentException("Use MuxedImpl"); throw new UnsupportedOperationException("Use MuxedImpl");
} }
/** @throws UnsupportedOperationException always, use MuxedImpl */
public boolean sendMessage(Destination dest, byte[] payload, int proto, int fromport, int toport) throws I2PSessionException { public boolean sendMessage(Destination dest, byte[] payload, int proto, int fromport, int toport) throws I2PSessionException {
throw new IllegalArgumentException("Use MuxedImpl"); throw new UnsupportedOperationException("Use MuxedImpl");
} }
/** @throws UnsupportedOperationException always, use MuxedImpl */
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent, public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent,
int proto, int fromport, int toport) throws I2PSessionException { int proto, int fromport, int toport) throws I2PSessionException {
throw new IllegalArgumentException("Use MuxedImpl"); throw new UnsupportedOperationException("Use MuxedImpl");
} }
/** @throws UnsupportedOperationException always, use MuxedImpl */
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent, long expire, public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent, long expire,
int proto, int fromport, int toport) throws I2PSessionException { int proto, int fromport, int toport) throws I2PSessionException {
throw new IllegalArgumentException("Use MuxedImpl"); throw new UnsupportedOperationException("Use MuxedImpl");
} }
/** @throws UnsupportedOperationException always, use MuxedImpl */
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent, long expire, public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent, long expire,
int proto, int fromport, int toport, int flags) throws I2PSessionException { int proto, int fromport, int toport, int flags) throws I2PSessionException {
throw new IllegalArgumentException("Use MuxedImpl"); throw new UnsupportedOperationException("Use MuxedImpl");
} }
/** @throws UnsupportedOperationException always, use MuxedImpl */
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, public boolean sendMessage(Destination dest, byte[] payload, int offset, int size,
int proto, int fromport, int toport, SendMessageOptions options) throws I2PSessionException { int proto, int fromport, int toport, SendMessageOptions options) throws I2PSessionException {
throw new IllegalArgumentException("Use MuxedImpl"); throw new UnsupportedOperationException("Use MuxedImpl");
}
/** @throws UnsupportedOperationException always, use MuxedImpl */
public long sendMessage(Destination dest, byte[] payload, int offset, int size,
int proto, int fromport, int toport,
SendMessageOptions options, SendMessageStatusListener listener) throws I2PSessionException {
throw new UnsupportedOperationException("Use MuxedImpl");
} }
/** unused, see MuxedImpl override */ /** unused, see MuxedImpl override */
@ -210,8 +271,8 @@ class I2PSessionImpl2 extends I2PSessionImpl {
String d = dest.calculateHash().toBase64().substring(0,4); String d = dest.calculateHash().toBase64().substring(0,4);
_log.info("sending message to: " + d + " compress? " + sc + " sizeIn=" + size + " sizeOut=" + compressed); _log.info("sending message to: " + d + " compress? " + sc + " sizeIn=" + size + " sizeOut=" + compressed);
} }
_context.statManager().addRateData("i2cp.tx.msgCompressed", compressed, 0); _context.statManager().addRateData("i2cp.tx.msgCompressed", compressed);
_context.statManager().addRateData("i2cp.tx.msgExpanded", size, 0); _context.statManager().addRateData("i2cp.tx.msgExpanded", size);
if (_noEffort) if (_noEffort)
return sendNoEffort(dest, payload, expires, 0); return sendNoEffort(dest, payload, expires, 0);
else else
@ -257,142 +318,29 @@ class I2PSessionImpl2 extends I2PSessionImpl {
*/ */
protected boolean sendBestEffort(Destination dest, byte payload[], long expires, int flags) protected boolean sendBestEffort(Destination dest, byte payload[], long expires, int flags)
throws I2PSessionException { throws I2PSessionException {
//SessionKey key = null;
//SessionKey newKey = null;
//SessionTag tag = null;
//Set sentTags = null;
//int oldTags = 0;
long begin = _context.clock().now();
/***********
if (I2CPMessageProducer.END_TO_END_CRYPTO) {
if (_log.shouldLog(Log.DEBUG)) _log.debug("begin sendBestEffort");
key = _context.sessionKeyManager().getCurrentKey(dest.getPublicKey());
if (_log.shouldLog(Log.DEBUG)) _log.debug("key fetched");
if (key == null) key = _context.sessionKeyManager().createSession(dest.getPublicKey());
tag = _context.sessionKeyManager().consumeNextAvailableTag(dest.getPublicKey(), key);
if (_log.shouldLog(Log.DEBUG)) _log.debug("tag consumed");
sentTags = null;
oldTags = _context.sessionKeyManager().getAvailableTags(dest.getPublicKey(), key);
long availTimeLeft = _context.sessionKeyManager().getAvailableTimeLeft(dest.getPublicKey(), key);
if ( (tagsSent == null) || (tagsSent.isEmpty()) ) { long nonce = _sendMessageNonce.incrementAndGet();
if (oldTags < NUM_TAGS) {
sentTags = createNewTags(NUM_TAGS);
if (_log.shouldLog(Log.DEBUG))
_log.debug("** sendBestEffort only had " + oldTags + " with " + availTimeLeft + ", adding " + NUM_TAGS + ": " + sentTags);
} else if (availTimeLeft < 2 * 60 * 1000) {
// if we have > 50 tags, but they expire in under 2 minutes, we want more
sentTags = createNewTags(NUM_TAGS);
if (_log.shouldLog(Log.DEBUG))
_log.debug(getPrefix() + "Tags expiring in " + availTimeLeft + ", adding " + NUM_TAGS + " new ones: " + sentTags);
//_log.error("** sendBestEffort available time left " + availTimeLeft);
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("sendBestEffort old tags: " + oldTags + " available time left: " + availTimeLeft);
}
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("sendBestEffort is sending " + tagsSent.size() + " with " + availTimeLeft
+ "ms left, " + oldTags + " tags known and "
+ (tag == null ? "no tag" : " a valid tag"));
}
if (false) // rekey
newKey = _context.keyGenerator().generateSessionKey();
if ( (tagsSent != null) && (!tagsSent.isEmpty()) ) {
if (sentTags == null)
sentTags = new HashSet();
sentTags.addAll(tagsSent);
}
} else {
// not using end to end crypto, so don't ever bundle any tags
}
**********/
//if (_log.shouldLog(Log.DEBUG)) _log.debug("before creating nonce");
long nonce = _context.random().nextInt(Integer.MAX_VALUE - 1) + 1;
//if (_log.shouldLog(Log.DEBUG)) _log.debug("before sync state");
MessageState state = new MessageState(_context, nonce, getPrefix()); MessageState state = new MessageState(_context, nonce, getPrefix());
//state.setKey(key);
//state.setTags(sentTags);
//state.setNewKey(newKey);
state.setTo(dest);
//if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "Setting key = " + key);
//if (keyUsed != null) {
//if (I2CPMessageProducer.END_TO_END_CRYPTO) {
// if (newKey != null)
// keyUsed.setData(newKey.getData());
// else
// keyUsed.setData(key.getData());
//} else {
// keyUsed.setData(SessionKey.INVALID_KEY.getData());
//}
//}
//if (tagsSent != null) {
// if (sentTags != null) {
// tagsSent.addAll(sentTags);
// }
//}
//if (_log.shouldLog(Log.DEBUG)) _log.debug("before sync state");
long beforeSendingSync = _context.clock().now();
long inSendingSync = 0;
synchronized (_sendingStates) {
inSendingSync = _context.clock().now();
_sendingStates.add(state);
}
long afterSendingSync = _context.clock().now();
if (_log.shouldLog(Log.DEBUG))
_log.debug(getPrefix() + "Adding sending state " + state.getMessageId() + " / "
+ state.getNonce() + " for best effort "
+ " sync took " + (inSendingSync-beforeSendingSync)
+ " add took " + (afterSendingSync-inSendingSync));
//_producer.sendMessage(this, dest, nonce, payload, tag, key, sentTags, newKey, expires);
_producer.sendMessage(this, dest, nonce, payload, expires, flags);
// since this is 'best effort', all we're waiting for is a status update // since this is 'best effort', all we're waiting for is a status update
// saying that the router received it - in theory, that should come back // saying that the router received it - in theory, that should come back
// immediately, but in practice can take up to a second (though usually // immediately, but in practice can take up to a second (though usually
// much quicker). setting this to false will short-circuit that delay // much quicker). setting this to false will short-circuit that delay
boolean actuallyWait = false; // true; boolean actuallyWait = false; // true;
long beforeWaitFor = _context.clock().now();
if (actuallyWait) if (actuallyWait)
state.waitFor(MessageStatusMessage.STATUS_SEND_ACCEPTED, _sendingStates.put(Long.valueOf(nonce), state);
_context.clock().now() + getTimeout()); _producer.sendMessage(this, dest, nonce, payload, expires, flags);
//long afterWaitFor = _context.clock().now();
//long inRemovingSync = 0;
synchronized (_sendingStates) {
//inRemovingSync = _context.clock().now();
_sendingStates.remove(state);
}
long afterRemovingSync = _context.clock().now();
boolean found = !actuallyWait || state.received(MessageStatusMessage.STATUS_SEND_ACCEPTED);
if (_log.shouldLog(Log.DEBUG))
_log.debug(getPrefix() + "After waitFor sending state " + state.getMessageId()
+ " / " + state.getNonce() + " found = " + found);
long timeToSend = afterRemovingSync - beforeSendingSync; if (actuallyWait) {
if ( (timeToSend > 10*1000) && (_log.shouldLog(Log.WARN)) ) { try {
_log.warn("wtf, took " + timeToSend + "ms to send the message?!", new Exception("baz")); state.waitForAccept(_context.clock().now() + getTimeout());
} catch (InterruptedException ie) {
throw new I2PSessionException("interrupted");
} finally {
_sendingStates.remove(Long.valueOf(nonce));
}
} }
boolean found = !actuallyWait || state.wasAccepted();
if ( (afterRemovingSync - begin > 500) && (_log.shouldLog(Log.WARN) ) ) {
_log.warn("Took " + (afterRemovingSync-begin) + "ms to sendBestEffort, "
+ (afterSendingSync-begin) + "ms to prepare, "
+ (beforeWaitFor-afterSendingSync) + "ms to send, "
+ (afterRemovingSync-beforeWaitFor) + "ms waiting for reply");
}
_context.statManager().addRateData("i2cp.sendBestEffortTotalTime", afterRemovingSync - begin, 0);
//_context.statManager().addRateData("i2cp.sendBestEffortStage0", beforeSendingSync- begin, 0);
//_context.statManager().addRateData("i2cp.sendBestEffortStage1", afterSendingSync- beforeSendingSync, 0);
//_context.statManager().addRateData("i2cp.sendBestEffortStage2", beforeWaitFor- afterSendingSync, 0);
//_context.statManager().addRateData("i2cp.sendBestEffortStage3", afterWaitFor- beforeWaitFor, 0);
//_context.statManager().addRateData("i2cp.sendBestEffortStage4", afterRemovingSync- afterWaitFor, 0);
if (found) { if (found) {
if (_log.shouldLog(Log.INFO)) if (_log.shouldLog(Log.INFO))
@ -402,9 +350,9 @@ class I2PSessionImpl2 extends I2PSessionImpl {
if (_log.shouldLog(Log.INFO)) if (_log.shouldLog(Log.INFO))
_log.info(getPrefix() + "Message send failed after " + state.getElapsed() + "ms with " _log.info(getPrefix() + "Message send failed after " + state.getElapsed() + "ms with "
+ payload.length + " bytes"); + payload.length + " bytes");
if (_log.shouldLog(Log.ERROR)) //if (_log.shouldLog(Log.ERROR))
_log.error(getPrefix() + "Never received *accepted* from the router! dropping and reconnecting"); // _log.error(getPrefix() + "Never received *accepted* from the router! dropping and reconnecting");
disconnect(); //disconnect();
return false; return false;
} }
return found; return found;
@ -432,8 +380,6 @@ class I2PSessionImpl2 extends I2PSessionImpl {
* Even when using sendBestEffort(), this is a waste, because the * Even when using sendBestEffort(), this is a waste, because the
* MessageState is removed from _sendingStates immediately and * MessageState is removed from _sendingStates immediately and
* so the lookup here fails. * so the lookup here fails.
* And iterating through the HashSet instead of having a map
* is bad too.
* *
* This is now pretty much avoided since streaming now sets * This is now pretty much avoided since streaming now sets
* i2cp.messageReliability = none, which forces sendNoEffort() instead of sendBestEffort(), * i2cp.messageReliability = none, which forces sendNoEffort() instead of sendBestEffort(),
@ -443,32 +389,24 @@ class I2PSessionImpl2 extends I2PSessionImpl {
*/ */
@Override @Override
public void receiveStatus(int msgId, long nonce, int status) { public void receiveStatus(int msgId, long nonce, int status) {
if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "Received status " + status + " for msgId " + msgId + " / " + nonce); if (_log.shouldLog(Log.DEBUG))
_log.debug(getPrefix() + "Received status " + status + " for msgId " + msgId + " / " + nonce);
MessageState state = null; MessageState state = null;
long beforeSync = _context.clock().now(); if ((state = _sendingStates.get(Long.valueOf(nonce))) != null) {
long inSync = 0; if (_log.shouldLog(Log.DEBUG))
synchronized (_sendingStates) { _log.debug(getPrefix() + "Found a matching state");
inSync = _context.clock().now(); } else if (!_sendingStates.isEmpty()) {
for (Iterator<MessageState> iter = _sendingStates.iterator(); iter.hasNext();) { // O(n**2)
state = iter.next(); // shouldn't happen, router sends good nonce for all statuses as of 0.9.14
if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "State " + state.getMessageId() + " / " + state.getNonce()); for (MessageState s : _sendingStates.values()) {
if (state.getNonce() == nonce) { if (s.getMessageId() != null && s.getMessageId().getMessageId() == msgId) {
if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "Found a matching state");
break;
} else if ((state.getMessageId() != null) && (state.getMessageId().getMessageId() == msgId)) {
if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "Found a matching state by msgId"); if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "Found a matching state by msgId");
state = s;
break; break;
} else {
if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "State does not match");
state = null;
} }
} }
} }
long afterSync = _context.clock().now();
if (_log.shouldLog(Log.DEBUG))
_log.debug("receiveStatus(" + msgId + ", " + nonce + ", " + status+ "): sync: "
+ (inSync-beforeSync) + "ms, check: " + (afterSync-inSync));
if (state != null) { if (state != null) {
if (state.getMessageId() == null) { if (state.getMessageId() == null) {
@ -477,11 +415,13 @@ class I2PSessionImpl2 extends I2PSessionImpl {
state.setMessageId(id); state.setMessageId(id);
} }
state.receive(status); state.receive(status);
if (state.wasSuccessful())
_sendingStates.remove(Long.valueOf(nonce));
long lifetime = state.getElapsed(); long lifetime = state.getElapsed();
switch (status) { switch (status) {
case 1: case 1:
_context.statManager().addRateData("i2cp.receiveStatusTime.1", lifetime, 0); _context.statManager().addRateData("i2cp.receiveStatusTime.1", lifetime);
break; break;
// best effort codes unused // best effort codes unused
//case 2: //case 2:
@ -491,10 +431,10 @@ class I2PSessionImpl2 extends I2PSessionImpl {
// _context.statManager().addRateData("i2cp.receiveStatusTime.3", lifetime, 0); // _context.statManager().addRateData("i2cp.receiveStatusTime.3", lifetime, 0);
// break; // break;
case 4: case 4:
_context.statManager().addRateData("i2cp.receiveStatusTime.4", lifetime, 0); _context.statManager().addRateData("i2cp.receiveStatusTime.4", lifetime);
break; break;
case 5: case 5:
_context.statManager().addRateData("i2cp.receiveStatusTime.5", lifetime, 0); _context.statManager().addRateData("i2cp.receiveStatusTime.5", lifetime);
break; break;
} }
@ -503,7 +443,6 @@ class I2PSessionImpl2 extends I2PSessionImpl {
_log.info(getPrefix() + "No matching state for messageId " + msgId + " / " + nonce _log.info(getPrefix() + "No matching state for messageId " + msgId + " / " + nonce
+ " w/ status = " + status); + " w/ status = " + status);
} }
_context.statManager().addRateData("i2cp.receiveStatusTime", _context.clock().now() - beforeSync, 0);
} }
/** /**
@ -522,11 +461,11 @@ class I2PSessionImpl2 extends I2PSessionImpl {
private void clearStates() { private void clearStates() {
if (_sendingStates == null) // only null if overridden by I2PSimpleSession if (_sendingStates == null) // only null if overridden by I2PSimpleSession
return; return;
synchronized (_sendingStates) { for (MessageState state : _sendingStates.values()) {
for (MessageState state : _sendingStates) state.cancel();
state.cancel();
if (_log.shouldLog(Log.INFO)) _log.info(getPrefix() + "Disconnecting " + _sendingStates.size() + " states");
_sendingStates.clear();
} }
if (_log.shouldLog(Log.INFO))
_log.info(getPrefix() + "Disconnecting " + _sendingStates.size() + " states");
_sendingStates.clear();
} }
} }

View File

@ -193,21 +193,7 @@ class I2PSessionMuxedImpl extends I2PSessionImpl2 {
SessionKey keyUsed, Set tagsSent, long expires, SessionKey keyUsed, Set tagsSent, long expires,
int proto, int fromPort, int toPort, int flags) int proto, int fromPort, int toPort, int flags)
throws I2PSessionException { throws I2PSessionException {
if (isClosed()) throw new I2PSessionException("Already closed"); payload = prepPayload(payload, offset, size, proto, fromPort, toPort);
updateActivity();
boolean sc = shouldCompress(size);
if (sc)
payload = DataHelper.compress(payload, offset, size);
else
payload = DataHelper.compress(payload, offset, size, DataHelper.NO_COMPRESSION);
setProto(payload, proto);
setFromPort(payload, fromPort);
setToPort(payload, toPort);
_context.statManager().addRateData("i2cp.tx.msgCompressed", payload.length, 0);
_context.statManager().addRateData("i2cp.tx.msgExpanded", size, 0);
if (_noEffort) if (_noEffort)
return sendNoEffort(dest, payload, expires, flags); return sendNoEffort(dest, payload, expires, flags);
else else
@ -232,11 +218,48 @@ class I2PSessionMuxedImpl extends I2PSessionImpl2 {
@Override @Override
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, public boolean sendMessage(Destination dest, byte[] payload, int offset, int size,
int proto, int fromPort, int toPort, SendMessageOptions options) throws I2PSessionException { int proto, int fromPort, int toPort, SendMessageOptions options) throws I2PSessionException {
payload = prepPayload(payload, offset, size, proto, fromPort, toPort);
//if (_noEffort) {
sendNoEffort(dest, payload, options);
return true;
//} else {
// unimplemented
//return sendBestEffort(dest, payload, options);
//}
}
/**
* Send a message and request an asynchronous notification of delivery status.
*
* See I2PSessionMuxedImpl for proto/port details.
* See SendMessageOptions for option details.
*
* @return the message ID to be used for later notification to the listener
* @throws I2PSessionException on all errors
* @since 0.9.14
*/
@Override
public long sendMessage(Destination dest, byte[] payload, int offset, int size,
int proto, int fromPort, int toPort,
SendMessageOptions options, SendMessageStatusListener listener) throws I2PSessionException {
payload = prepPayload(payload, offset, size, proto, fromPort, toPort);
long nonce = _sendMessageNonce.incrementAndGet();
long expires = Math.max(_context.clock().now() + 60*1000L, options.getTime());
MessageState state = new MessageState(_context, nonce, this, expires, listener);
_sendingStates.put(Long.valueOf(nonce), state);
_producer.sendMessage(this, dest, nonce, payload, options);
return nonce;
}
/**
* @return gzip compressed payload, ready to send
* @since 0.9.14
*/
private byte[] prepPayload(byte[] payload, int offset, int size, int proto, int fromPort, int toPort) throws I2PSessionException {
if (isClosed()) throw new I2PSessionException("Already closed"); if (isClosed()) throw new I2PSessionException("Already closed");
updateActivity(); updateActivity();
boolean sc = shouldCompress(size); if (shouldCompress(size))
if (sc)
payload = DataHelper.compress(payload, offset, size); payload = DataHelper.compress(payload, offset, size);
else else
payload = DataHelper.compress(payload, offset, size, DataHelper.NO_COMPRESSION); payload = DataHelper.compress(payload, offset, size, DataHelper.NO_COMPRESSION);
@ -245,15 +268,9 @@ class I2PSessionMuxedImpl extends I2PSessionImpl2 {
setFromPort(payload, fromPort); setFromPort(payload, fromPort);
setToPort(payload, toPort); setToPort(payload, toPort);
_context.statManager().addRateData("i2cp.tx.msgCompressed", payload.length, 0); _context.statManager().addRateData("i2cp.tx.msgCompressed", payload.length);
_context.statManager().addRateData("i2cp.tx.msgExpanded", size, 0); _context.statManager().addRateData("i2cp.tx.msgExpanded", size);
//if (_noEffort) { return payload;
sendNoEffort(dest, payload, options);
return true;
//} else {
// unimplemented
//return sendBestEffort(dest, payload, options);
//}
} }
/** /**

View File

@ -1,12 +1,8 @@
package net.i2p.client; package net.i2p.client;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
import net.i2p.I2PAppContext; import net.i2p.I2PAppContext;
import net.i2p.data.Destination;
import net.i2p.data.SessionKey;
import net.i2p.data.i2cp.MessageId; import net.i2p.data.i2cp.MessageId;
import net.i2p.data.i2cp.MessageStatusMessage; import net.i2p.data.i2cp.MessageStatusMessage;
import net.i2p.util.Log; import net.i2p.util.Log;
@ -14,8 +10,10 @@ import net.i2p.util.Log;
/** /**
* Contains the state of a payload message being sent to a peer. * Contains the state of a payload message being sent to a peer.
* *
* This is mostly unused. See sendNoEffort vs. sendBestEffort in I2PSessionImpl2. * Originally was a general-purpose waiter.
* TODO delete altogether? This is really bad. * Then we got rid of guaranteed delivery.
* Then we stopped waiting for accept in best-effort delivery.
* Brought back to life for asynchronous status delivery to the client.
*/ */
class MessageState { class MessageState {
private final I2PAppContext _context; private final I2PAppContext _context;
@ -23,32 +21,59 @@ class MessageState {
private final long _nonce; private final long _nonce;
private final String _prefix; private final String _prefix;
private MessageId _id; private MessageId _id;
private final Set<Integer> _receivedStatus;
private SessionKey _key;
private SessionKey _newKey;
private Set _tags;
private Destination _to;
private boolean _cancelled;
private final long _created; private final long _created;
private final long _expires;
private final SendMessageStatusListener _listener;
private final I2PSession _session;
private static final AtomicLong __stateId = new AtomicLong(); private enum State { INIT, ACCEPTED, PROBABLE_FAIL, FAIL, SUCCESS };
private final long _stateId; private State _state = State.INIT;
/**
* For synchronous waiting for accept with waitForAccept().
* UNUSED.
*/
public MessageState(I2PAppContext ctx, long nonce, String prefix) { public MessageState(I2PAppContext ctx, long nonce, String prefix) {
_stateId = __stateId.incrementAndGet();
_context = ctx; _context = ctx;
_log = ctx.logManager().getLog(MessageState.class); _log = ctx.logManager().getLog(MessageState.class);
_nonce = nonce; _nonce = nonce;
_prefix = prefix + "[" + _stateId + "]: "; _prefix = prefix + '[' + _nonce + "]: ";
_receivedStatus = new HashSet<Integer>();
_created = ctx.clock().now(); _created = ctx.clock().now();
//ctx.statManager().createRateStat("i2cp.checkStatusTime", "how long it takes to go through the states", "i2cp", new long[] { 60*1000 }); _expires = _created + 60*1000L;
_listener = null;
_session = null;
}
/**
* For asynchronous notification
* @param expires absolute time (not interval)
* @since 0.9.14
*/
public MessageState(I2PAppContext ctx, long nonce, I2PSession session,
long expires, SendMessageStatusListener listener) {
_context = ctx;
_log = ctx.logManager().getLog(MessageState.class);
_nonce = nonce;
_prefix = session.toString() + " [" + _nonce + "]: ";
_created = ctx.clock().now();
_expires = expires;
_listener = listener;
_session = session;
} }
public void receive(int status) { public void receive(int status) {
synchronized (_receivedStatus) { State oldState;
_receivedStatus.add(Integer.valueOf(status)); State newState;
_receivedStatus.notifyAll(); synchronized (this) {
oldState = _state;
locked_update(status);
newState = _state;
this.notifyAll();
}
if (_listener != null) {
// only notify on changing state, and only if we haven't expired
if (oldState != newState && _expires > _context.clock().now())
_listener.messageStatus(_session, _nonce, status);
} }
} }
@ -60,221 +85,114 @@ class MessageState {
return _id; return _id;
} }
public long getNonce() {
return _nonce;
}
/** @deprecated unused */
public void setKey(SessionKey key) {
if (_log.shouldLog(Log.DEBUG))
_log.debug(_prefix + "Setting key [" + _key + "] to [" + key + "]");
_key = key;
}
/** @deprecated unused */
public SessionKey getKey() {
return _key;
}
/** @deprecated unused */
public void setNewKey(SessionKey key) {
_newKey = key;
}
/** @deprecated unused */
public SessionKey getNewKey() {
return _newKey;
}
/** @deprecated unused */
public void setTags(Set tags) {
_tags = tags;
}
/** @deprecated unused */
public Set getTags() {
return _tags;
}
public void setTo(Destination dest) {
_to = dest;
}
/** @deprecated unused */
public Destination getTo() {
return _to;
}
public long getElapsed() { public long getElapsed() {
return _context.clock().now() - _created; return _context.clock().now() - _created;
} }
public void waitFor(int status, long expiration) { /**
//long checkTime = -1; * @since 0.9.14
boolean found = false; */
while (!found) { public long getExpires() {
if (_cancelled) return; return _expires;
}
/**
* For guaranteed/best effort only. Not really used.
*/
public void waitForAccept(long expiration) throws InterruptedException {
while (true) {
long timeToWait = expiration - _context.clock().now(); long timeToWait = expiration - _context.clock().now();
if (timeToWait <= 0) { if (timeToWait <= 0) {
if (_log.shouldLog(Log.WARN)) if (_log.shouldLog(Log.WARN))
_log.warn(_prefix + "Expired waiting for the status [" + status + "]"); _log.warn(_prefix + "Expired waiting for the status");
return; return;
} }
found = false; synchronized (this) {
synchronized (_receivedStatus) { if (_state != State.INIT) {
//long beforeCheck = _context.clock().now();
if (locked_isSuccess(status) || locked_isFailure(status)) {
if (_log.shouldLog(Log.DEBUG)) if (_log.shouldLog(Log.DEBUG))
_log.debug(_prefix + "Received a confirm (one way or the other)"); _log.debug(_prefix + "Received a confirm (one way or the other)");
found = true; return;
}
//checkTime = _context.clock().now() - beforeCheck;
if (!found) {
if (timeToWait > 5000) {
timeToWait = 5000;
}
try {
_receivedStatus.wait(timeToWait);
} catch (InterruptedException ie) { // nop
}
} }
if (timeToWait > 5000)
timeToWait = 5000;
this.wait(timeToWait);
} }
//if (found)
// _context.statManager().addRateData("i2cp.checkStatusTime", checkTime, 0);
} }
} }
private boolean locked_isSuccess(int wantedStatus) { /**
boolean rv = false; * Update our flags
* @since 0.9.14
*/
private void locked_update(int status) {
switch (status) {
case MessageStatusMessage.STATUS_SEND_ACCEPTED:
// only trumps init
if (_state == State.INIT)
_state = State.ACCEPTED;
break;
if (_log.shouldLog(Log.DEBUG)) case MessageStatusMessage.STATUS_SEND_BEST_EFFORT_FAILURE:
_log.debug(_prefix + "isSuccess(" + wantedStatus + "): " + _receivedStatus); case MessageStatusMessage.STATUS_SEND_GUARANTEED_FAILURE:
for (Integer val : _receivedStatus) { // does not trump failure or success
int recv = val.intValue(); if (_state != State.FAIL && _state != State.SUCCESS)
switch (recv) { _state = State.PROBABLE_FAIL;
case MessageStatusMessage.STATUS_SEND_BEST_EFFORT_FAILURE: break;
if (_log.shouldLog(Log.WARN))
_log.warn(_prefix + "Received best effort failure after " + getElapsed() + " from " case MessageStatusMessage.STATUS_SEND_FAILURE_LOCAL:
+ toString()); case MessageStatusMessage.STATUS_SEND_FAILURE_ROUTER:
rv = false; case MessageStatusMessage.STATUS_SEND_FAILURE_NETWORK:
break; case MessageStatusMessage.STATUS_SEND_FAILURE_BAD_SESSION:
case MessageStatusMessage.STATUS_SEND_GUARANTEED_FAILURE: case MessageStatusMessage.STATUS_SEND_FAILURE_BAD_MESSAGE:
if (_log.shouldLog(Log.WARN)) case MessageStatusMessage.STATUS_SEND_FAILURE_BAD_OPTIONS:
_log.warn(_prefix + "Received guaranteed failure after " + getElapsed() + " from " case MessageStatusMessage.STATUS_SEND_FAILURE_OVERFLOW:
+ toString()); case MessageStatusMessage.STATUS_SEND_FAILURE_EXPIRED:
rv = false; case MessageStatusMessage.STATUS_SEND_FAILURE_LOCAL_LEASESET:
break; case MessageStatusMessage.STATUS_SEND_FAILURE_NO_TUNNELS:
case MessageStatusMessage.STATUS_SEND_ACCEPTED: case MessageStatusMessage.STATUS_SEND_FAILURE_UNSUPPORTED_ENCRYPTION:
if (wantedStatus == MessageStatusMessage.STATUS_SEND_ACCEPTED) { case MessageStatusMessage.STATUS_SEND_FAILURE_DESTINATION:
return true; // if we're only looking for accepted, take it directly (don't let any GUARANTEED_* override it) case MessageStatusMessage.STATUS_SEND_FAILURE_BAD_LEASESET:
} case MessageStatusMessage.STATUS_SEND_FAILURE_EXPIRED_LEASESET:
// ignore accepted, as we want something better case MessageStatusMessage.STATUS_SEND_FAILURE_NO_LEASESET:
if (_log.shouldLog(Log.DEBUG)) case SendMessageStatusListener.STATUS_CANCELLED:
_log.debug(_prefix + "Got accepted, but we're waiting for more from " + toString()); // does not trump success
continue; if (_state != State.SUCCESS)
case MessageStatusMessage.STATUS_SEND_BEST_EFFORT_SUCCESS: _state = State.FAIL;
if (_log.shouldLog(Log.DEBUG)) break;
_log.debug(_prefix + "Received best effort success after " + getElapsed()
+ " from " + toString()); case MessageStatusMessage.STATUS_SEND_BEST_EFFORT_SUCCESS:
if (wantedStatus == recv) { case MessageStatusMessage.STATUS_SEND_GUARANTEED_SUCCESS:
rv = true; case MessageStatusMessage.STATUS_SEND_SUCCESS_LOCAL:
} else { // trumps all
if (_log.shouldLog(Log.DEBUG)) _state = State.SUCCESS;
_log.debug(_prefix + "Not guaranteed success, but best effort after "
+ getElapsed() + " will do... from " + toString()); default:
rv = true; break;
}
break;
case MessageStatusMessage.STATUS_SEND_GUARANTEED_SUCCESS:
if (_log.shouldLog(Log.DEBUG))
_log.debug(_prefix + "Received guaranteed success after " + getElapsed() + " from "
+ toString());
// even if we're waiting for best effort success, guaranteed is good enough
rv = true;
break;
case -1:
continue;
default:
if (_log.shouldLog(Log.DEBUG))
_log.debug(_prefix + "Received something else [" + recv + "]...");
}
} }
return rv;
} }
private boolean locked_isFailure(int wantedStatus) { /**
boolean rv = false; * @return true if accepted (fixme and not failed)
* @since 0.9.14
if (_log.shouldLog(Log.DEBUG)) */
_log.debug(_prefix + "isFailure(" + wantedStatus + "): " + _receivedStatus); public boolean wasAccepted() {
synchronized (this) {
for (Integer val : _receivedStatus) { return _state != State.INIT && _state != State.FAIL;
int recv = val.intValue();
switch (recv) {
case MessageStatusMessage.STATUS_SEND_BEST_EFFORT_FAILURE:
if (_log.shouldLog(Log.DEBUG))
_log.warn(_prefix + "Received best effort failure after " + getElapsed() + " from "
+ toString());
rv = true;
break;
case MessageStatusMessage.STATUS_SEND_GUARANTEED_FAILURE:
if (_log.shouldLog(Log.DEBUG))
_log.warn(_prefix + "Received guaranteed failure after " + getElapsed() + " from "
+ toString());
rv = true;
break;
case MessageStatusMessage.STATUS_SEND_ACCEPTED:
if (wantedStatus == MessageStatusMessage.STATUS_SEND_ACCEPTED) {
rv = false;
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug(_prefix + "Got accepted, but we're waiting for more from "
+ toString());
continue;
// ignore accepted, as we want something better
}
break;
case MessageStatusMessage.STATUS_SEND_BEST_EFFORT_SUCCESS:
if (_log.shouldLog(Log.DEBUG))
_log.debug(_prefix + "Received best effort success after " + getElapsed()
+ " from " + toString());
if (wantedStatus == recv) {
rv = false;
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug(_prefix + "Not guaranteed success, but best effort after "
+ getElapsed() + " will do... from " + toString());
rv = false;
}
break;
case MessageStatusMessage.STATUS_SEND_GUARANTEED_SUCCESS:
if (_log.shouldLog(Log.DEBUG))
_log.debug(_prefix + "Received guaranteed success after " + getElapsed() + " from "
+ toString());
// even if we're waiting for best effort success, guaranteed is good enough
rv = false;
break;
case -1:
continue;
default:
if (_log.shouldLog(Log.DEBUG))
_log.debug(_prefix + "Received something else [" + recv + "]...");
}
} }
return rv;
} }
/** #return true if the given status (or an equivalent) was received */ /**
public boolean received(int status) { * @return true if successful
synchronized (_receivedStatus) { * @since 0.9.14
return locked_isSuccess(status); */
public boolean wasSuccessful() {
synchronized (this) {
return _state == State.SUCCESS;
} }
} }
public void cancel() { public void cancel() {
_cancelled = true; // Inject a fake status
synchronized (_receivedStatus) { receive(SendMessageStatusListener.STATUS_CANCELLED);
_receivedStatus.notifyAll();
}
} }
} }

View File

@ -0,0 +1,25 @@
package net.i2p.client;
/**
* Asynchronously notify the client of the status
* of a sent message.
*
* @since 0.9.14
*/
public interface SendMessageStatusListener {
/** I2CP status codes are 0 - 255. Start our fake ones at 256. */
public static final int STATUS_CANCELLED = 256;
/**
* Tell the client of an update in the send status for a message
* previously sent with I2PSession.sendMessage().
* Multiple calls for a single message ID are possible.
*
* @param session session notifying
* @param msgId message number returned from a previous sendMessage() call
* @param status of the message, as defined in MessageStatusMessage and this class.
*/
void messageStatus(I2PSession session, long msgId, int status);
}