diff --git a/router/java/src/net/i2p/router/crypto/ratchet/ECIESAEADEngine.java b/router/java/src/net/i2p/router/crypto/ratchet/ECIESAEADEngine.java index ab0cdd98d99e69abe77c9fc16e260c5d5c7f2c0b..e58bae2accd7a3d7adf67f9431f318088fa36f80 100644 --- a/router/java/src/net/i2p/router/crypto/ratchet/ECIESAEADEngine.java +++ b/router/java/src/net/i2p/router/crypto/ratchet/ECIESAEADEngine.java @@ -304,7 +304,7 @@ public final class ECIESAEADEngine { // tell the SKM PublicKey bob = new PublicKey(EncType.ECIES_X25519, bobPK); - keyManager.createSession(bob, state); + keyManager.createSession(bob, state, null); if (pc.cloveSet.isEmpty()) { if (_log.shouldWarn()) @@ -430,7 +430,7 @@ public final class ECIESAEADEngine { // tell the SKM PublicKey bob = new PublicKey(EncType.ECIES_X25519, bobPK); - keyManager.updateSession(bob, oldState, state); + keyManager.updateSession(bob, oldState, state, null); if (pc.cloveSet.isEmpty()) { if (_log.shouldWarn()) @@ -558,13 +558,15 @@ public final class ECIESAEADEngine { * @param target public key to which the data should be encrypted. * @param priv local private key to encrypt with, from the leaseset * @param replyDI non-null to request an ack, or null + * @param callback may be null * @return encrypted data or null on failure * */ public byte[] encrypt(CloveSet cloves, PublicKey target, PrivateKey priv, - RatchetSKM keyManager, DeliveryInstructions replyDI) { + RatchetSKM keyManager, DeliveryInstructions replyDI, + ReplyCallback callback) { try { - return x_encrypt(cloves, target, priv, keyManager, replyDI); + return x_encrypt(cloves, target, priv, keyManager, replyDI, callback); } catch (Exception e) { _log.error("ECIES encrypt error", e); return null; @@ -572,7 +574,8 @@ public final class ECIESAEADEngine { } private byte[] x_encrypt(CloveSet cloves, PublicKey target, PrivateKey priv, - RatchetSKM keyManager, DeliveryInstructions replyDI) { + RatchetSKM keyManager, DeliveryInstructions replyDI, + ReplyCallback callback) { if (target.getType() != EncType.ECIES_X25519) throw new IllegalArgumentException(); if (Arrays.equals(target.getData(), NULLPK)) { @@ -586,7 +589,7 @@ public final class ECIESAEADEngine { if (_log.shouldDebug()) _log.debug("Encrypting as NS to " + target); // no ack in NS - return encryptNewSession(cloves, target, priv, keyManager, null); + return encryptNewSession(cloves, target, priv, keyManager, null, callback); } HandshakeState state = re.key.getHandshakeState(); @@ -601,11 +604,11 @@ public final class ECIESAEADEngine { if (_log.shouldDebug()) _log.debug("Encrypting as NSR to " + target + " with tag " + re.tag.toBase64()); // no ack in NSR - return encryptNewSessionReply(cloves, target, state, re.tag, keyManager, null); + return encryptNewSessionReply(cloves, target, state, re.tag, keyManager, null, callback); } if (_log.shouldDebug()) _log.debug("Encrypting as ES to " + target + " with key " + re.key + " and tag " + re.tag.toBase64()); - byte rv[] = encryptExistingSession(cloves, target, re, replyDI); + byte rv[] = encryptExistingSession(cloves, target, re, replyDI, callback); return rv; } @@ -625,10 +628,12 @@ public final class ECIESAEADEngine { * </pre> * * @param replyDI non-null to request an ack, or null + * @param callback may be null * @return encrypted data or null on failure */ private byte[] encryptNewSession(CloveSet cloves, PublicKey target, PrivateKey priv, - RatchetSKM keyManager, DeliveryInstructions replyDI) { + RatchetSKM keyManager, DeliveryInstructions replyDI, + ReplyCallback callback) { HandshakeState state; try { state = new HandshakeState(HandshakeState.PATTERN_ID_IK, HandshakeState.INITIATOR, _edhThread); @@ -667,7 +672,7 @@ public final class ECIESAEADEngine { _log.debug("Elligator2 encoded eph. key: " + Base64.encode(enc, 0, 32)); // tell the SKM - keyManager.createSession(target, state); + keyManager.createSession(target, state, callback); return enc; } @@ -689,11 +694,12 @@ public final class ECIESAEADEngine { * * @param state must have already been cloned * @param replyDI non-null to request an ack, or null + * @param callback may be null * @return encrypted data or null on failure */ private byte[] encryptNewSessionReply(CloveSet cloves, PublicKey target, HandshakeState state, RatchetSessionTag currentTag, RatchetSKM keyManager, - DeliveryInstructions replyDI) { + DeliveryInstructions replyDI, ReplyCallback callback) { if (_log.shouldDebug()) _log.debug("State before encrypt new session reply: " + state); byte[] tag = currentTag.getData(); @@ -746,7 +752,7 @@ public final class ECIESAEADEngine { return null; } // tell the SKM - keyManager.updateSession(target, null, state); + keyManager.updateSession(target, null, state, callback); return enc; } @@ -765,7 +771,7 @@ public final class ECIESAEADEngine { * @return encrypted data or null on failure */ private byte[] encryptExistingSession(CloveSet cloves, PublicKey target, RatchetEntry re, - DeliveryInstructions replyDI) { + DeliveryInstructions replyDI, ReplyCallback callback) { // if (ACKREQ_IN_ES && replyDI == null) replyDI = new DeliveryInstructions(); @@ -774,6 +780,9 @@ public final class ECIESAEADEngine { SessionKeyAndNonce key = re.key; byte encr[] = encryptAEADBlock(rawTag, payload, key, key.getNonce()); System.arraycopy(rawTag, 0, encr, 0, TAGLEN); + if (callback != null) { + // TODO + } return encr; } diff --git a/router/java/src/net/i2p/router/crypto/ratchet/RatchetSKM.java b/router/java/src/net/i2p/router/crypto/ratchet/RatchetSKM.java index 4c0b2e8162460f9fcdf2c29dc1fc17172196d220..bd363dda028c98a8794e4a733d8cc72adb7a885f 100644 --- a/router/java/src/net/i2p/router/crypto/ratchet/RatchetSKM.java +++ b/router/java/src/net/i2p/router/crypto/ratchet/RatchetSKM.java @@ -15,7 +15,6 @@ import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicInteger; import com.southernstorm.noise.protocol.HandshakeState; @@ -48,9 +47,6 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener private final ConcurrentHashMap<RatchetSessionTag, RatchetTagSet> _inboundTagSets; protected final I2PAppContext _context; private volatile boolean _alive; - /** for debugging */ - private final AtomicInteger _rcvTagSetID = new AtomicInteger(); - private final AtomicInteger _sentTagSetID = new AtomicInteger(); private final HKDF _hkdf; /** @@ -168,15 +164,16 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener * For inbound (NS rcvd), if no other pending outbound sessions, creates one * and returns true, or false if one already exists. * + * @param callback null for inbound, may be null for outbound */ - boolean createSession(PublicKey target, HandshakeState state) { + boolean createSession(PublicKey target, HandshakeState state, ReplyCallback callback) { EncType type = target.getType(); if (type != EncType.ECIES_X25519) throw new IllegalArgumentException("Bad public key type " + type); + OutboundSession sess = new OutboundSession(target, null, state, callback); boolean isInbound = state.getRole() == HandshakeState.RESPONDER; if (isInbound) { // we are Bob, NS received - OutboundSession sess = new OutboundSession(target, null, state); boolean rv = addSession(sess, true); if (_log.shouldInfo()) { if (rv) @@ -187,7 +184,6 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener return rv; } else { // we are Alice, NS sent - OutboundSession sess = new OutboundSession(target, null, state); synchronized (_pendingOutboundSessions) { List<OutboundSession> pending = _pendingOutboundSessions.get(target); if (pending != null) { @@ -215,7 +211,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener * @param oldState null for inbound, pre-clone for outbound * @return true if this was the first NSR received */ - boolean updateSession(PublicKey target, HandshakeState oldState, HandshakeState state) { + boolean updateSession(PublicKey target, HandshakeState oldState, HandshakeState state, ReplyCallback callback) { EncType type = target.getType(); if (type != EncType.ECIES_X25519) throw new IllegalArgumentException("Bad public key type " + type); @@ -231,7 +227,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener // TODO can we recover? return false; } - sess.updateSession(state); + sess.updateSession(state, callback); } else { // we are Alice, NSR received if (_log.shouldInfo()) @@ -251,7 +247,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener if (oldState.equals(pstate)) { if (!found) { found = true; - sess.updateSession(state); + sess.updateSession(state, null); boolean ok = addSession(sess, false); if (_log.shouldDebug()) { if (ok) @@ -407,13 +403,13 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener if (sess == null) { if (_log.shouldWarn()) _log.warn("No session for delivered RatchetTagSet to target: " + toString(target)); -/////////// + // TODO createSession(target, key); } else { sess.setCurrentKey(key); } -/////////// - RatchetTagSet set = new RatchetTagSet(_hkdf, key, key, _context.clock().now(), _sentTagSetID.incrementAndGet()); + // TODO + RatchetTagSet set = new RatchetTagSet(_hkdf, key, key, _context.clock().now(), 0); sess.addTags(set); if (_log.shouldDebug()) _log.debug("Tags delivered: " + set + @@ -812,6 +808,8 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener private class OutboundSession { private final PublicKey _target; private final HandshakeState _state; + private final ReplyCallback _NScallback; + private ReplyCallback _NSRcallback; private SessionKey _currentKey; private final long _established; private long _lastUsed; @@ -843,10 +841,17 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener private int _consecutiveFailures; private static final int MAX_FAILS = 2; + private static final int DEBUG_OB_NSR = 0x10001; + private static final int DEBUG_IB_NSR = 0x10002; - public OutboundSession(PublicKey target, SessionKey key, HandshakeState state) { + /** + * @param key may be null + * @param callback may be null. Always null for IB. + */ + public OutboundSession(PublicKey target, SessionKey key, HandshakeState state, ReplyCallback callback) { _target = target; _currentKey = key; + _NScallback = callback; _established = _context.clock().now(); _lastUsed = _established; _unackedTagSets = new HashSet<RatchetTagSet>(4); @@ -863,7 +868,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener // This is an INBOUND NS, we make an OUTBOUND tagset for the NSR RatchetTagSet tagset = new RatchetTagSet(_hkdf, state, rk, tk, - _established, _sentTagSetID.getAndIncrement()); + _established, DEBUG_OB_NSR); _tagSets.add(tagset); _state = null; if (_log.shouldDebug()) @@ -873,7 +878,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener // This is an OUTBOUND NS, we make an INBOUND tagset for the NSR RatchetTagSet tagset = new RatchetTagSet(_hkdf, RatchetSKM.this, state, rk, tk, - _established, _rcvTagSetID.getAndIncrement(), + _established, DEBUG_IB_NSR, MIN_RCV_WINDOW_NSR, MAX_RCV_WINDOW_NSR); // store the state so we can find the right session when we receive the NSR _state = state; @@ -888,8 +893,9 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener * For inbound (NSR sent by Bob), sets up inbound ES tagset. * * @param state current state + * @param callback only for inbound (NSR sent by Bob), may be null */ - void updateSession(HandshakeState state) { + void updateSession(HandshakeState state, ReplyCallback callback) { byte[] ck = state.getChainingKey(); byte[] k_ab = new byte[32]; byte[] k_ba = new byte[32]; @@ -901,26 +907,27 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener // We are Bob // This is an OUTBOUND NSR, we make an INBOUND tagset for ES RatchetTagSet tagset_ab = new RatchetTagSet(_hkdf, RatchetSKM.this, _target, rk, new SessionKey(k_ab), - now, _rcvTagSetID.getAndIncrement(), + now, 0, MIN_RCV_WINDOW_ES, MAX_RCV_WINDOW_ES); // and a pending outbound one RatchetTagSet tagset_ba = new RatchetTagSet(_hkdf, rk, new SessionKey(k_ba), - now, _sentTagSetID.getAndIncrement()); + now, 0); if (_log.shouldDebug()) { _log.debug("Update IB Session, rk = " + rk + " tk = " + Base64.encode(k_ab) + " ES tagset: " + tagset_ab); _log.debug("Pending OB Session, rk = " + rk + " tk = " + Base64.encode(k_ba) + " ES tagset: " + tagset_ba); } synchronized (_tagSets) { _unackedTagSets.add(tagset_ba); + _NSRcallback = callback; } } else { // We are Alice // This is an INBOUND NSR, we make an OUTBOUND tagset for ES RatchetTagSet tagset_ab = new RatchetTagSet(_hkdf, rk, new SessionKey(k_ab), - now, _sentTagSetID.getAndIncrement()); + now, 0); // and an inbound one RatchetTagSet tagset_ba = new RatchetTagSet(_hkdf, RatchetSKM.this, _target, rk, new SessionKey(k_ba), - now, _rcvTagSetID.getAndIncrement(), + now, 0, MIN_RCV_WINDOW_ES, MAX_RCV_WINDOW_ES); if (_log.shouldDebug()) { _log.debug("Update OB Session, rk = " + rk + " tk = " + Base64.encode(k_ab) + " ES tagset: " + tagset_ab); @@ -932,6 +939,9 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener } // We can't destroy the original state, as more NSRs may come in //_state.destroy(); + // Bob received the NS, call the callback + if (_NScallback != null) + _NScallback.onReply(); } // kills the keys for future NSRs //state.destroy(); @@ -955,6 +965,10 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener _unackedTagSets.clear(); _tagSets.clear(); _tagSets.add(obSet); + if (_NSRcallback != null) { + _NSRcallback.onReply(); + _NSRcallback = null; + } return; } } diff --git a/router/java/src/net/i2p/router/crypto/ratchet/RatchetTagSet.java b/router/java/src/net/i2p/router/crypto/ratchet/RatchetTagSet.java index 8107ca478c647f2bc7d702211b8db99b16ddcb3c..306c723b962b83102f2f9f57127f2570fc2f6b14 100644 --- a/router/java/src/net/i2p/router/crypto/ratchet/RatchetTagSet.java +++ b/router/java/src/net/i2p/router/crypto/ratchet/RatchetTagSet.java @@ -6,6 +6,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; import com.southernstorm.noise.protocol.DHState; import com.southernstorm.noise.protocol.HandshakeState; @@ -60,6 +61,9 @@ class RatchetTagSet implements TagSetHandle { private KeyPair _nextKeys; private NextSessionKey _nextKey; private boolean _nextKeyAcked; + /** for debugging */ + private static final AtomicInteger __tagSetID = new AtomicInteger(); + private final int _tagSetID = __tagSetID.incrementAndGet(); private static final String INFO_1 = "KDFDHRatchetStep"; private static final String INFO_2 = "TagAndKeyGenKeys"; @@ -432,7 +436,7 @@ class RatchetTagSet implements TagSetHandle { */ public boolean getAcked() { return _acked; } - /** for debugging */ + /** the Key ID */ public int getID() { return _id; } @@ -448,7 +452,8 @@ class RatchetTagSet implements TagSetHandle { buf.append("NSR ").append(_state.hashCode()).append(' '); else buf.append("ES "); - buf.append("TagSet #").append(_id) + buf.append("TagSet #").append(_tagSetID) + .append(" keyID #").append(_id) .append("\nCreated: ").append(DataHelper.formatTime(_created)) .append("\nLast use: ").append(DataHelper.formatTime(_date)); PublicKey pk = getRemoteKey(); diff --git a/router/java/src/net/i2p/router/crypto/ratchet/ReplyCallback.java b/router/java/src/net/i2p/router/crypto/ratchet/ReplyCallback.java new file mode 100644 index 0000000000000000000000000000000000000000..3f8d7a64b3427ac959859e482a1d4de4567e8700 --- /dev/null +++ b/router/java/src/net/i2p/router/crypto/ratchet/ReplyCallback.java @@ -0,0 +1,20 @@ +package net.i2p.router.crypto.ratchet; + +/** + * ECIES will call this back if an ack was requested and received. + * + * @since 0.9.46 + */ +public interface ReplyCallback { + + /** + * When does this callback expire? + * @return java time + */ + public long getExpiration(); + + /** + * A reply was received. + */ + public void onReply(); +} diff --git a/router/java/src/net/i2p/router/crypto/ratchet/SessionTagListener.java b/router/java/src/net/i2p/router/crypto/ratchet/SessionTagListener.java index e304fbfb0d6fddabf80ec1ac4cec5b872e385d58..a251d7629827aed977311de9f4ebe7d3e922a72c 100644 --- a/router/java/src/net/i2p/router/crypto/ratchet/SessionTagListener.java +++ b/router/java/src/net/i2p/router/crypto/ratchet/SessionTagListener.java @@ -1,7 +1,5 @@ package net.i2p.router.crypto.ratchet; -import net.i2p.data.SessionTag; - /** * Something that looks for SessionTags. * diff --git a/router/java/src/net/i2p/router/message/GarlicMessageBuilder.java b/router/java/src/net/i2p/router/message/GarlicMessageBuilder.java index dbd612734d3509a43e478397298e8543fd47f5f9..57342962e68d070c0d5fc94884552bdcc5ebb1a3 100644 --- a/router/java/src/net/i2p/router/message/GarlicMessageBuilder.java +++ b/router/java/src/net/i2p/router/message/GarlicMessageBuilder.java @@ -32,6 +32,7 @@ import net.i2p.router.LeaseSetKeys; import net.i2p.router.RouterContext; import net.i2p.router.crypto.ratchet.MuxedSKM; import net.i2p.router.crypto.ratchet.RatchetSKM; +import net.i2p.router.crypto.ratchet.ReplyCallback; import net.i2p.util.Log; /** @@ -246,20 +247,21 @@ public class GarlicMessageBuilder { /** * ECIES_X25519 only. - * Called by GarlicMessageBuilder only. + * Called by OCMJH only. * * @param ctx scope * @param config how/what to wrap * @param target public key of the location being garlic routed to (may be null if we * know the encryptKey and encryptTag) * @param replyDI non-null to request an ack, or null + * @param callback may be null * @return null if expired or on other errors * @throws IllegalArgumentException on error * @since 0.9.44 */ static GarlicMessage buildECIESMessage(RouterContext ctx, GarlicConfig config, PublicKey target, Hash from, SessionKeyManager skm, - DeliveryInstructions replyDI) { + DeliveryInstructions replyDI, ReplyCallback callback) { PublicKey key = config.getRecipientPublicKey(); if (key.getType() != EncType.ECIES_X25519) throw new IllegalArgumentException(); @@ -289,7 +291,7 @@ public class GarlicMessageBuilder { log.warn("No SKM for " + from.toBase32()); return null; } - byte encData[] = ctx.eciesEngine().encrypt(cloveSet, target, priv, rskm, replyDI); + byte encData[] = ctx.eciesEngine().encrypt(cloveSet, target, priv, rskm, replyDI, callback); if (encData == null) { if (log.shouldWarn()) log.warn("Encrypt fail for " + from.toBase32()); diff --git a/router/java/src/net/i2p/router/message/OutboundClientMessageJobHelper.java b/router/java/src/net/i2p/router/message/OutboundClientMessageJobHelper.java index 9c906c5e19822a42fca56af14e46e5a679b86771..30079313e863c5c85b486dd708691e8924e57093 100644 --- a/router/java/src/net/i2p/router/message/OutboundClientMessageJobHelper.java +++ b/router/java/src/net/i2p/router/message/OutboundClientMessageJobHelper.java @@ -30,6 +30,7 @@ import net.i2p.data.i2np.I2NPMessage; import net.i2p.router.LeaseSetKeys; import net.i2p.router.RouterContext; import net.i2p.router.TunnelInfo; +import net.i2p.router.crypto.ratchet.ReplyCallback; import net.i2p.router.networkdb.kademlia.MessageWrapper; import net.i2p.util.Log; @@ -108,12 +109,14 @@ class OutboundClientMessageJobHelper { * @param replyTunnel non-null if requireAck is true or bundledReplyLeaseSet is non-null * @param requireAck if true, bundle replyToken in an ack clove * @param bundledReplyLeaseSet may be null; if non-null, put it in a clove + * @param callback only for ECIES, may be null * @return garlic, or null if no tunnels were found (or other errors) */ static GarlicMessage createGarlicMessage(RouterContext ctx, long replyToken, long expiration, PublicKey recipientPK, PayloadGarlicConfig dataClove, Hash from, Destination dest, TunnelInfo replyTunnel, int tagsToSendOverride, int lowTagsOverride, SessionKey wrappedKey, - Set<SessionTag> wrappedTags, boolean requireAck, LeaseSet bundledReplyLeaseSet) { + Set<SessionTag> wrappedTags, boolean requireAck, LeaseSet bundledReplyLeaseSet, + ReplyCallback callback) { SessionKeyManager skm = ctx.clientManager().getClientSessionKeyManager(from); if (skm == null) @@ -147,7 +150,7 @@ class OutboundClientMessageJobHelper { } else { di = null; } - msg = GarlicMessageBuilder.buildECIESMessage(ctx, config, recipientPK, from, skm, di); + msg = GarlicMessageBuilder.buildECIESMessage(ctx, config, recipientPK, from, skm, di, callback); } else { // no use sending tags unless we have a reply token set up already int tagsToSend = replyToken >= 0 ? (tagsToSendOverride > 0 ? tagsToSendOverride : skm.getTagsToSend()) : 0; diff --git a/router/java/src/net/i2p/router/message/OutboundClientMessageOneShotJob.java b/router/java/src/net/i2p/router/message/OutboundClientMessageOneShotJob.java index 05f43b7c0a46ecdb70720e34b5a251e1789ff533..9676d24e724a2c89d93b9be3d0262f0f0006457d 100644 --- a/router/java/src/net/i2p/router/message/OutboundClientMessageOneShotJob.java +++ b/router/java/src/net/i2p/router/message/OutboundClientMessageOneShotJob.java @@ -38,6 +38,7 @@ import net.i2p.router.ReplyJob; import net.i2p.router.Router; import net.i2p.router.RouterContext; import net.i2p.router.TunnelInfo; +import net.i2p.router.crypto.ratchet.ReplyCallback; import net.i2p.util.Log; /** @@ -655,12 +656,18 @@ public class OutboundClientMessageOneShotJob extends JobImpl { // Per-message flag > 0 overrides per-session option int tagsToSend = SendMessageOptions.getTagsToSend(sendFlags); + ReplyCallback callback; + if (wantACK && _encryptionKey.getType() == EncType.ECIES_X25519) { + callback = new ECIESReplyCallback(replyLeaseSet); + } else { + callback = null; + } GarlicMessage msg = OutboundClientMessageJobHelper.createGarlicMessage(getContext(), token, _overallExpiration, _encryptionKey, clove, _from.calculateHash(), _to, _inTunnel, tagsToSend, tagsRequired, sessKey, tags, - wantACK, replyLeaseSet); + wantACK, replyLeaseSet, callback); if (msg == null) { // set to null if there are no tunnels to ack the reply back through // (should we always fail for this? or should we send it anyway, even if @@ -675,9 +682,9 @@ public class OutboundClientMessageOneShotJob extends JobImpl { //if (_log.shouldLog(Log.DEBUG)) // _log.debug(getJobId() + ": send() - token expected " + token + " to " + _toString); - SendSuccessJob onReply = null; - SendTimeoutJob onFail = null; - ReplySelector selector = null; + SendSuccessJob onReply; + SendTimeoutJob onFail; + ReplySelector selector; if (wantACK && _encryptionKey.getType() == EncType.ELGAMAL_2048) { TagSetHandle tsh = null; @@ -686,10 +693,14 @@ public class OutboundClientMessageOneShotJob extends JobImpl { if (skm != null) tsh = skm.tagsDelivered(_encryptionKey, sessKey, tags); } - onFail = new SendTimeoutJob(getContext(), sessKey, tsh); - onReply = new SendSuccessJob(getContext(), sessKey, tsh, replyLeaseSet, onFail); + onFail = new SendTimeoutJob(sessKey, tsh); + onReply = new SendSuccessJob(sessKey, tsh, replyLeaseSet, onFail); long expiration = Math.max(_overallExpiration, _start + REPLY_TIMEOUT_MS_MIN); selector = new ReplySelector(token, expiration); + } else { + onReply = null; + onFail = null; + selector = null; } if (_log.shouldLog(Log.DEBUG)) @@ -698,7 +709,7 @@ public class OutboundClientMessageOneShotJob extends JobImpl { + _lease.getTunnelId() + " on " + _lease.getGateway()); - DispatchJob dispatchJob = new DispatchJob(getContext(), msg, selector, onReply, onFail); + DispatchJob dispatchJob = new DispatchJob(msg, selector, onReply, onFail); //if (false) // dispatch may take 100+ms, so toss it in its own job // getContext().jobQueue().addJob(dispatchJob); //else @@ -723,9 +734,9 @@ public class OutboundClientMessageOneShotJob extends JobImpl { * @param success non-null if sel non-null * @param timeout non-null if sel non-null */ - public DispatchJob(RouterContext ctx, GarlicMessage msg, ReplySelector sel, + public DispatchJob(GarlicMessage msg, ReplySelector sel, SendSuccessJob success, SendTimeoutJob timeout) { - super(ctx); + super(OutboundClientMessageOneShotJob.this.getContext()); _msg = msg; _selector = sel; _replyFound = success; @@ -1014,11 +1025,11 @@ public class OutboundClientMessageOneShotJob extends JobImpl { * @param key may be null * @param tags may be null * @param ls the delivered leaseset or null - * @param timeout will be cancelled when this is run + * @param timeout will be cancelled when this is run, may be null */ - public SendSuccessJob(RouterContext enclosingContext, SessionKey key, + public SendSuccessJob(SessionKey key, TagSetHandle tags, LeaseSet ls, SendTimeoutJob timeout) { - super(enclosingContext); + super(OutboundClientMessageOneShotJob.this.getContext()); _key = key; _tags = tags; _deliveredLS = ls; @@ -1064,7 +1075,8 @@ public class OutboundClientMessageOneShotJob extends JobImpl { skm.tagsAcked(_encryptionKey, _key, _tags); } } - getContext().jobQueue().removeJob(_replyTimeout); + if (_replyTimeout != null) + getContext().jobQueue().removeJob(_replyTimeout); long sendTime = getContext().clock().now() - _start; if (old == Result.FAIL) { @@ -1110,6 +1122,26 @@ public class OutboundClientMessageOneShotJob extends JobImpl { public void setMessage(I2NPMessage msg) {} } + + /** + * For ECIES only. + * + * @since 0.9.46 + */ + private class ECIESReplyCallback extends SendSuccessJob implements ReplyCallback { + public ECIESReplyCallback(LeaseSet ls) { + super(null, null, ls, null); + } + + public long getExpiration() { + // same as SendTimeoutJob + return Math.max(_overallExpiration, _start + REPLY_TIMEOUT_MS_MIN); + } + + public void onReply() { + runJob(); + } + } /** * Fired after the basic timeout for sending through the given tunnel has been reached. @@ -1127,8 +1159,8 @@ public class OutboundClientMessageOneShotJob extends JobImpl { * @param key may be null * @param tags may be null */ - public SendTimeoutJob(RouterContext enclosingContext, SessionKey key, TagSetHandle tags) { - super(enclosingContext); + public SendTimeoutJob(SessionKey key, TagSetHandle tags) { + super(OutboundClientMessageOneShotJob.this.getContext()); _key = key; _tags = tags; }