package net.i2p.router.client;
/*
* 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.Date;
import net.i2p.data.Lease;
import net.i2p.data.LeaseSet;
import net.i2p.data.i2cp.I2CPMessage;
import net.i2p.data.i2cp.I2CPMessageException;
import net.i2p.data.i2cp.RequestLeaseSetMessage;
import net.i2p.data.i2cp.RequestVariableLeaseSetMessage;
import net.i2p.data.i2cp.SessionId;
import net.i2p.router.JobImpl;
import net.i2p.router.RouterContext;
import net.i2p.util.Log;
/**
* Async job to walk the client through generating a lease set. First sends it
* to the client and then queues up a CheckLeaseRequestStatus job for
* processing after the expiration. When that CheckLeaseRequestStatus is run,
* if the client still hasn't provided the signed leaseSet, fire off the onFailed
* job from the intermediary LeaseRequestState and drop the client.
*
*/
class RequestLeaseSetJob extends JobImpl {
private final Log _log;
private final ClientConnectionRunner _runner;
private final LeaseRequestState _requestState;
private static final long MAX_FUDGE = 2*1000;
/** temp for testing */
private static final String PROP_VARIABLE = "router.variableLeaseExpiration";
private static final boolean DFLT_VARIABLE = true;
public RequestLeaseSetJob(RouterContext ctx, ClientConnectionRunner runner, LeaseRequestState state) {
super(ctx);
_log = ctx.logManager().getLog(RequestLeaseSetJob.class);
_runner = runner;
_requestState = state;
// all createRateStat in ClientManager
}
public String getName() { return "Request Lease Set"; }
public void runJob() {
if (_runner.isDead()) return;
LeaseSet requested = _requestState.getRequested();
long endTime = requested.getEarliestLeaseDate();
// Add a small number of ms (0 to MAX_FUDGE) that increases as we approach the expire time.
// Since the earliest date functions as a version number,
// this will force the floodfill to flood each new version;
// otherwise it won't if the earliest time hasn't changed.
long fudge = MAX_FUDGE - ((endTime - getContext().clock().now()) / (10*60*1000 / MAX_FUDGE));
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("Adding fudge " + fudge);
endTime += fudge;
SessionId id = _runner.getSessionId(requested.getDestination().calculateHash());
if (id == null) {
_runner.failLeaseRequest(_requestState);
return;
}
I2CPMessage msg;
if (getContext().getProperty(PROP_VARIABLE, DFLT_VARIABLE) &&
(_runner instanceof QueuedClientConnectionRunner ||
RequestVariableLeaseSetMessage.isSupported(_runner.getClientVersion()))) {
// new style - leases will have individual expirations
RequestVariableLeaseSetMessage rmsg = new RequestVariableLeaseSetMessage();
rmsg.setSessionId(id);
for (int i = 0; i < requested.getLeaseCount(); i++) {
Lease lease = requested.getLease(i);
if (lease.getEndDate().getTime() < endTime) {
// don't modify old object, we don't know where it came from
Lease nl = new Lease();
nl.setGateway(lease.getGateway());
nl.setTunnelId(lease.getTunnelId());
nl.setEndDate(new Date(endTime));
lease = nl;
//if (_log.shouldLog(Log.INFO))
// _log.info("Adjusted end date to " + endTime + " for " + lease);
}
rmsg.addEndpoint(lease);
}
msg = rmsg;
} else {
// old style - all leases will have same expiration
RequestLeaseSetMessage rmsg = new RequestLeaseSetMessage();
Date end = new Date(endTime);
rmsg.setEndDate(end);
rmsg.setSessionId(id);
for (int i = 0; i < requested.getLeaseCount(); i++) {
Lease lease = requested.getLease(i);
rmsg.addEndpoint(lease.getGateway(),
lease.getTunnelId());
}
msg = rmsg;
}
try {
//_runner.setLeaseRequest(state);
_runner.doSend(msg);
getContext().jobQueue().addJob(new CheckLeaseRequestStatus());
} catch (I2CPMessageException ime) {
getContext().statManager().addRateData("client.requestLeaseSetDropped", 1);
_log.error("Error sending I2CP message requesting the lease set", ime);
_requestState.setIsSuccessful(false);
if (_requestState.getOnFailed() != null)
RequestLeaseSetJob.this.getContext().jobQueue().addJob(_requestState.getOnFailed());
_runner.failLeaseRequest(_requestState);
// Don't disconnect, the tunnel will retry
//_runner.disconnectClient("I2CP error requesting leaseSet");
}
}
/**
* Schedule this job to be run after the request's expiration, so that if
* it wasn't yet successful, we fire off the failure job and disconnect the
* client (but if it was, noop)
*
*/
private class CheckLeaseRequestStatus extends JobImpl {
private final long _start;
public CheckLeaseRequestStatus() {
super(RequestLeaseSetJob.this.getContext());
_start = System.currentTimeMillis();
getTiming().setStartAfter(_requestState.getExpiration());
}
public void runJob() {
if (_runner.isDead()) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Already dead, dont try to expire the leaseSet lookup");
return;
}
if (_requestState.getIsSuccessful()) {
// we didn't fail
CheckLeaseRequestStatus.this.getContext().statManager().addRateData("client.requestLeaseSetSuccess", 1);
return;
} else {
CheckLeaseRequestStatus.this.getContext().statManager().addRateData("client.requestLeaseSetTimeout", 1);
if (_log.shouldLog(Log.ERROR)) {
long waited = System.currentTimeMillis() - _start;
_log.error("Failed to receive a leaseSet in the time allotted (" + waited + "): " + _requestState);
}
if (_requestState.getOnFailed() != null)
RequestLeaseSetJob.this.getContext().jobQueue().addJob(_requestState.getOnFailed());
_runner.failLeaseRequest(_requestState);
// Don't disconnect, the tunnel will retry
//_runner.disconnectClient("Took too long to request leaseSet");
}
}
public String getName() { return "Check LeaseRequest Status"; }
}
}