package net.i2p.router.message;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.util.HashSet;
import java.util.Set;
import net.i2p.data.Certificate;
import net.i2p.data.Hash;
import net.i2p.data.PublicKey;
import net.i2p.data.router.RouterInfo;
import net.i2p.data.SessionKey;
import net.i2p.data.SessionTag;
import net.i2p.data.i2np.DeliveryInstructions;
import net.i2p.data.i2np.DeliveryStatusMessage;
import net.i2p.data.i2np.I2NPMessage;
import net.i2p.router.Job;
import net.i2p.router.JobImpl;
import net.i2p.router.MessageSelector;
import net.i2p.router.ReplyJob;
import net.i2p.router.Router;
import net.i2p.router.RouterContext;
import net.i2p.router.peermanager.PeerProfile;
import net.i2p.util.Log;
/**
* Build a test message that will be sent to the target to make sure they're alive.
* Once that is verified, onSendJob is enqueued. If their reachability isn't
* known (or they're unreachable) within timeoutMs, onSendFailedJob is enqueued.
* The test message is sent at the specified priority.
*
*/
public class BuildTestMessageJob extends JobImpl {
private Log _log;
private RouterInfo _target;
private Hash _replyTo;
private Job _onSend;
private Job _onSendFailed;
private long _timeoutMs;
private int _priority;
private long _testMessageKey;
/**
*
* @param target router being tested
* @param onSendJob after the ping is successful
* @param onSendFailedJob after the ping fails or times out
* @param timeoutMs how long to wait before timing out
* @param priority how high priority to send this test
*/
public BuildTestMessageJob(RouterContext ctx, RouterInfo target, Hash replyTo,
Job onSendJob, Job onSendFailedJob, long timeoutMs, int priority) {
super(ctx);
_log = ctx.logManager().getLog(BuildTestMessageJob.class);
_target = target;
_replyTo = replyTo;
_onSend = onSendJob;
_onSendFailed = onSendFailedJob;
_timeoutMs = timeoutMs;
_priority = priority;
_testMessageKey = -1;
}
public String getName() { return "Build Test Message"; }
public void runJob() {
if (alreadyKnownReachable()) {
getContext().jobQueue().addJob(_onSend);
return;
}
// This is a test message - build a garlic with a DeliveryStatusMessage that
// first goes to the peer then back to us.
if (_log.shouldLog(Log.DEBUG))
_log.debug("Building garlic message to test " + _target.getIdentity().getHash().toBase64());
GarlicConfig config = buildGarlicCloveConfig();
// TODO: make the last params on this specify the correct sessionKey and tags used
ReplyJob replyJob = new JobReplyJob(getContext(), _onSend, config.getRecipient().getIdentity().getPublicKey(), config.getId(), null, new HashSet<SessionTag>());
MessageSelector sel = buildMessageSelector();
SendGarlicJob job = new SendGarlicJob(getContext(), config, null, _onSendFailed, replyJob, _onSendFailed, _timeoutMs, _priority, sel);
getContext().jobQueue().addJob(job);
}
private boolean alreadyKnownReachable() {
PeerProfile profile = getContext().profileOrganizer().getProfile(_target.getIdentity().getHash());
if ( (profile == null) || (!profile.getIsActive()) )
return false;
else
return true;
}
private MessageSelector buildMessageSelector() {
return new TestMessageSelector(_testMessageKey, _timeoutMs + getContext().clock().now());
}
private GarlicConfig buildGarlicCloveConfig() {
_testMessageKey = getContext().random().nextLong(I2NPMessage.MAX_ID_VALUE);
if (_log.shouldLog(Log.INFO))
_log.info("Test message key: " + _testMessageKey);
GarlicConfig config = new GarlicConfig();
PayloadGarlicConfig ackClove = buildAckClove();
config.addClove(ackClove);
DeliveryInstructions instructions = new DeliveryInstructions();
instructions.setDeliveryMode(DeliveryInstructions.DELIVERY_MODE_ROUTER);
instructions.setDelayRequested(false);
instructions.setDelaySeconds(0);
instructions.setEncrypted(false);
instructions.setEncryptionKey(null);
instructions.setRouter(_target.getIdentity().getHash());
instructions.setTunnelId(null);
config.setCertificate(new Certificate(Certificate.CERTIFICATE_TYPE_NULL, null));
config.setDeliveryInstructions(instructions);
config.setId(getContext().random().nextLong(I2NPMessage.MAX_ID_VALUE));
config.setExpiration(_timeoutMs+getContext().clock().now()+2*Router.CLOCK_FUDGE_FACTOR);
config.setRecipient(_target);
return config;
}
/**
* Build a clove that sends a DeliveryStatusMessage to us
*/
private PayloadGarlicConfig buildAckClove() {
PayloadGarlicConfig ackClove = new PayloadGarlicConfig();
DeliveryInstructions ackInstructions = new DeliveryInstructions();
ackInstructions.setDeliveryMode(DeliveryInstructions.DELIVERY_MODE_ROUTER);
ackInstructions.setRouter(_replyTo); // yikes!
ackInstructions.setDelayRequested(false);
ackInstructions.setDelaySeconds(0);
ackInstructions.setEncrypted(false);
DeliveryStatusMessage msg = new DeliveryStatusMessage(getContext());
msg.setArrival(getContext().clock().now());
msg.setMessageId(_testMessageKey);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Delivery status message key: " + _testMessageKey + " arrival: " + msg.getArrival());
ackClove.setCertificate(new Certificate(Certificate.CERTIFICATE_TYPE_NULL, null));
ackClove.setDeliveryInstructions(ackInstructions);
ackClove.setExpiration(_timeoutMs+getContext().clock().now());
ackClove.setId(getContext().random().nextLong(I2NPMessage.MAX_ID_VALUE));
ackClove.setPayload(msg);
ackClove.setRecipient(_target);
return ackClove;
}
/**
* Search inbound messages for delivery status messages with our key
*/
private final static class TestMessageSelector implements MessageSelector {
private long _testMessageKey;
private long _timeout;
public TestMessageSelector(long key, long timeout) {
_testMessageKey = key;
_timeout = timeout;
}
public boolean continueMatching() { return false; }
public long getExpiration() { return _timeout; }
public boolean isMatch(I2NPMessage inMsg) {
if (inMsg.getType() == DeliveryStatusMessage.MESSAGE_TYPE) {
return ((DeliveryStatusMessage)inMsg).getMessageId() == _testMessageKey;
} else {
return false;
}
}
}
/**
* On reply, fire off the specified job
*
*/
private static final class JobReplyJob extends JobImpl implements ReplyJob {
private Job _job;
private PublicKey _target;
private long _msgId;
private Set<SessionTag> _sessionTagsDelivered;
private SessionKey _keyDelivered;
public JobReplyJob(RouterContext ctx, Job job, PublicKey target, long msgId, SessionKey keyUsed, Set<SessionTag> tagsDelivered) {
super(ctx);
_job = job;
_target = target;
_msgId = msgId;
_keyDelivered = keyUsed;
_sessionTagsDelivered = tagsDelivered;
}
public String getName() { return "Reply To Test Message Received"; }
public void runJob() {
if ( (_keyDelivered != null) &&
(_sessionTagsDelivered != null) &&
(_sessionTagsDelivered.size() > 0) )
getContext().sessionKeyManager().tagsDelivered(_target, _keyDelivered, _sessionTagsDelivered);
getContext().jobQueue().addJob(_job);
}
public void setMessage(I2NPMessage message) {
// ignored, this is just a ping
}
}
}