package net.i2p.router.tunnel;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import net.i2p.data.Base64;
import net.i2p.data.Hash;
import net.i2p.data.TunnelId;
import net.i2p.router.RouterContext;
import net.i2p.router.TunnelInfo;
/**
* Coordinate the info that the tunnel creator keeps track of, including what
* peers are in the tunnel and what their configuration is
*
*/
public class TunnelCreatorConfig implements TunnelInfo {
protected final RouterContext _context;
/** only necessary for client tunnels */
private final Hash _destination;
/** gateway first */
private final HopConfig _config[];
/** gateway first */
private final Hash _peers[];
private volatile long _expiration;
private List<Integer> _order;
private long _replyMessageId;
private final boolean _isInbound;
private int _messagesProcessed;
private long _verifiedBytesTransferred;
private boolean _failed;
private int _failures;
private boolean _reused;
private int _priority;
//private static final int THROUGHPUT_COUNT = 3;
// Fastest 1 minute throughput, in bytes per minute, ordered with fastest first.
//private final double _peakThroughput[] = new double[THROUGHPUT_COUNT];
private long _peakThroughputCurrentTotal;
private long _peakThroughputLastCoallesce = System.currentTimeMillis();
// Make configurable? - but can't easily get to pool options from here
private static final int MAX_CONSECUTIVE_TEST_FAILURES = 3;
private static final SimpleDateFormat _fmt = new SimpleDateFormat("HH:mm:ss", Locale.UK);
/**
* For exploratory only (null destination)
* @param length 1 minimum (0 hop is length 1)
*/
public TunnelCreatorConfig(RouterContext ctx, int length, boolean isInbound) {
this(ctx, length, isInbound, null);
}
/**
* @param length 1 minimum (0 hop is length 1)
* @param destination null for exploratory
*/
public TunnelCreatorConfig(RouterContext ctx, int length, boolean isInbound, Hash destination) {
_context = ctx;
if (length <= 0)
throw new IllegalArgumentException("0 length? 0 hop tunnels are 1 length!");
_config = new HopConfig[length];
_peers = new Hash[length];
for (int i = 0; i < length; i++) {
_config[i] = new HopConfig();
}
_isInbound = isInbound;
_destination = destination;
}
/**
* How many hops are there in the tunnel?
* INCLUDING US.
* i.e. one more than the TunnelCreatorConfig length.
*/
public int getLength() { return _config.length; }
public Properties getOptions() { return null; }
/**
* retrieve the config for the given hop. the gateway is
* hop 0.
*/
public HopConfig getConfig(int hop) { return _config[hop]; }
/**
* retrieve the tunnelId that the given hop receives messages on.
* the gateway is hop 0.
*
*/
public TunnelId getReceiveTunnelId(int hop) { return _config[hop].getReceiveTunnel(); }
/**
* retrieve the tunnelId that the given hop sends messages on.
* the gateway is hop 0.
*
*/
public TunnelId getSendTunnelId(int hop) { return _config[hop].getSendTunnel(); }
/** retrieve the peer at the given hop. the gateway is hop 0 */
public Hash getPeer(int hop) { return _peers[hop]; }
public void setPeer(int hop, Hash peer) { _peers[hop] = peer; }
/**
* For convenience
* @return getPeer(0)
* @since 0.8.9
*/
public Hash getGateway() {
return _peers[0];
}
/**
* For convenience
* @return getPeer(getLength() - 1)
* @since 0.8.9
*/
public Hash getEndpoint() {
return _peers[_peers.length - 1];
}
/**
* For convenience
* @return isInbound() ? getGateway() : getEndpoint()
* @since 0.8.9
*/
public Hash getFarEnd() {
return _peers[_isInbound ? 0 : _peers.length - 1];
}
/** is this an inbound tunnel? */
public boolean isInbound() { return _isInbound; }
/**
* If this is a client tunnel, what destination is it for?
* @return null for exploratory
*/
public Hash getDestination() { return _destination; }
public long getExpiration() { return _expiration; }
public void setExpiration(long when) { _expiration = when; }
/** component ordering in the new style request */
public List<Integer> getReplyOrder() { return _order; }
public void setReplyOrder(List<Integer> order) { _order = order; }
/** new style reply message id */
public long getReplyMessageId() { return _replyMessageId; }
public void setReplyMessageId(long id) { _replyMessageId = id; }
/** take note of a message being pumped through this tunnel */
public synchronized void incrementProcessedMessages() { _messagesProcessed++; }
public synchronized int getProcessedMessagesCount() { return _messagesProcessed; }
/**
* This calls profile manager tunnelDataPushed1m() for each peer
*/
public synchronized void incrementVerifiedBytesTransferred(int bytes) {
_verifiedBytesTransferred += bytes;
_peakThroughputCurrentTotal += bytes;
long now = System.currentTimeMillis();
long timeSince = now - _peakThroughputLastCoallesce;
if (timeSince >= 60*1000) {
long tot = _peakThroughputCurrentTotal;
double normalized = tot * 60d*1000d / timeSince;
_peakThroughputLastCoallesce = now;
_peakThroughputCurrentTotal = 0;
if (_context != null) {
// skip ourselves
int start = _isInbound ? 0 : 1;
int end = _isInbound ? _peers.length - 1 : _peers.length;
for (int i = start; i < end; i++) {
_context.profileManager().tunnelDataPushed1m(_peers[i], (int)normalized);
}
}
}
}
public synchronized long getVerifiedBytesTransferred() { return _verifiedBytesTransferred; }
/**** unused
public synchronized double getPeakThroughputKBps() {
double rv = 0;
for (int i = 0; i < THROUGHPUT_COUNT; i++)
rv += _peakThroughput[i];
rv /= (60d*1024d*THROUGHPUT_COUNT);
return rv;
}
public synchronized void setPeakThroughputKBps(double kBps) {
_peakThroughput[0] = kBps*60*1024;
//for (int i = 0; i < THROUGHPUT_COUNT; i++)
// _peakThroughput[i] = kBps*60;
}
****/
/**
* The tunnel failed a test, so (maybe) stop using it
*/
public boolean tunnelFailed() {
_failures++;
if (_failures > MAX_CONSECUTIVE_TEST_FAILURES) {
_failed = true;
return false;
} else {
return true;
}
}
public boolean getTunnelFailed() { return _failed; }
public int getTunnelFailures() { return _failures; }
public void testSuccessful(int ms) {
if (!_failed)
_failures = 0;
}
/**
* Did we reuse this tunnel?
* @since 0.8.11
*/
public boolean wasReused() { return _reused; }
/**
* Note that we reused this tunnel
* @since 0.8.11
*/
public void setReused() { _reused = true; }
/**
* Outbound message priority - for outbound tunnels only
* @return -25 to +25, default 0
* @since 0.9.4
*/
public int getPriority() { return _priority; }
/**
* Outbound message priority - for outbound tunnels only
* @param priority -25 to +25, default 0
* @since 0.9.4
*/
public void setPriority(int priority) { _priority = priority; }
@Override
public String toString() {
// H0:1235-->H1:2345-->H2:2345
StringBuilder buf = new StringBuilder(128);
if (_isInbound)
buf.append("IB");
else
buf.append("OB");
if (_destination == null)
buf.append(" expl");
else
buf.append(" client ").append(Base64.encode(_destination.getData(), 0, 3));
buf.append(": GW ");
for (int i = 0; i < _peers.length; i++) {
buf.append(_peers[i].toBase64().substring(0,4));
buf.append(':');
if (_config[i].getReceiveTunnel() != null)
buf.append(_config[i].getReceiveTunnel());
else
buf.append("me");
if (_config[i].getSendTunnel() != null) {
buf.append('.');
buf.append(_config[i].getSendTunnel());
} else if (_isInbound || i == 0) {
buf.append(".me");
}
if (i + 1 < _peers.length)
buf.append("-->");
}
buf.append(" exp. ").append(getExpirationString());
if (_replyMessageId > 0)
buf.append(" replyMsgID ").append(_replyMessageId);
if (_messagesProcessed > 0)
buf.append(" with ").append(_messagesProcessed).append("/").append(_verifiedBytesTransferred).append(" msgs/bytes");
if (_failures > 0)
buf.append(" with ").append(_failures).append(" failures");
return buf.toString();
}
private String getExpirationString() {
return format(_expiration);
}
static String format(long date) {
Date d = new Date(date);
synchronized (_fmt) {
return _fmt.format(d);
}
}
}