package net.i2p.client.streaming.impl;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import net.i2p.I2PAppContext;
import static net.i2p.client.streaming.impl.I2PSocketOptionsImpl.getDouble;
import net.i2p.data.Destination;
import net.i2p.util.Log;
import net.i2p.util.SimpleTimer2;
/**
* Share important TCP Control Block parameters across Connections
* to the same remote peer.
* This is intended for "temporal" sharing at connection open/close time,
* not "ensemble" sharing during a connection. Ref. RFC 2140.
*
* There is a TCB share per ConnectionManager (i.e. per local Destination)
* so that there is no information leakage to other Destinations on the
* same router.
*
*/
class TCBShare {
private final I2PAppContext _context;
private final Log _log;
private final Map<Destination, Entry> _cache;
private final CleanEvent _cleaner;
private final double _rttDampening, _wdwDampening, _rttDevDampening;
private static final long EXPIRE_TIME = 15*60*1000;
private static final long CLEAN_TIME = 5*60*1000;
///// constants defined in rfc 2140
///// do not change unless you know what you're doing
private static final double RTT_DAMPENING = 0.75;
private static final double RTTDEV_DAMPENING = 0.75;
private static final double WDW_DAMPENING = 0.75;
private static final String RTT_DAMP_PROP="i2p.streaming.tcbcache.rttDampening";
private static final String WDW_DAMP_PROP="i2p.streaming.tcbcache.wdwDampening";
private static final String RTTDEV_DAMP_PROP="i2p.streaming.tcbcache.rttdevDampening";
/////
private static final int MAX_RTT = ((int) Connection.MAX_RESEND_DELAY) / 2;
private static final int MAX_RTT_DEV = (int) (MAX_RTT * 1.5);
private static final int MAX_WINDOW_SIZE = ConnectionPacketHandler.MAX_SLOW_START_WINDOW;
public TCBShare(I2PAppContext ctx, SimpleTimer2 timer) {
_context = ctx;
_log = ctx.logManager().getLog(TCBShare.class);
final Properties props = ctx.getProperties();
_rttDampening = getDouble(props, RTT_DAMP_PROP, RTT_DAMPENING);
_wdwDampening = getDouble(props, WDW_DAMP_PROP, WDW_DAMPENING);
_rttDevDampening = getDouble(props, RTTDEV_DAMP_PROP, RTTDEV_DAMPENING);
_cache = new ConcurrentHashMap<Destination,Entry>(4);
_cleaner = new CleanEvent(timer);
_cleaner.schedule(CLEAN_TIME);
if (_log.shouldLog(Log.DEBUG)) {
String log = "Creating TCBCache with rttDamp=%s, rttDevDamp=%s, wdwDamp=%s, "+
"expire=%d, clean=%d";
log = String.format(log,_rttDampening,_rttDevDampening,_wdwDampening,
EXPIRE_TIME,CLEAN_TIME);
_log.debug(log);
}
}
/**
* Cannot be restarted.
*/
public void stop() {
_cleaner.cancel();
_cache.clear();
}
/** retrieve from cache */
public void updateOptsFromShare(Connection con) {
Destination dest = con.getRemotePeer();
if (dest == null)
return;
ConnectionOptions opts = con.getOptions();
if (opts == null)
return;
Entry e = _cache.get(dest);
if (e == null || e.isExpired())
return;
final int rtt, rttDev, wdw;
synchronized(e) {
rtt = e.getRTT();
rttDev = e.getRTTDev();
wdw = e.getWindowSize();
}
if (_log.shouldLog(Log.DEBUG)) {
_log.debug("From cache: " +
con.getSession().getMyDestination().calculateHash().toBase64().substring(0, 4) +
'-' +
dest.calculateHash().toBase64().substring(0, 4) +
" RTT: " + rtt +
" RTTDev: "+ rttDev +
" wdw: " + wdw );
}
opts.loadFromCache(rtt,rttDev,wdw);
}
/** store to cache */
public void updateShareOpts(Connection con) {
Destination dest = con.getRemotePeer();
if (dest == null)
return;
if (con.getAckedPackets() <= 0)
return;
ConnectionOptions opts = con.getOptions();
if (opts == null)
return;
int old = -1;
int oldw = -1;
int oldDev = -1;
Entry e = _cache.get(dest);
if (e == null || e.isExpired()) {
e = new Entry(opts.getRTT(), opts.getWindowSize(), opts.getRTTDev());
_cache.put(dest, e);
} else {
synchronized(e) {
old = e.getRTT();
oldw = e.getWindowSize();
oldDev = e.getRTTDev();
e.setRTT(opts.getRTT());
e.setWindowSize(opts.getWindowSize());
e.setRTTDev(opts.getRTTDev());
}
}
if (_log.shouldLog(Log.DEBUG)) {
_log.debug("To cache: " +
con.getSession().getMyDestination().calculateHash().toBase64().substring(0, 4) +
'-' +
dest.calculateHash().toBase64().substring(0, 4) +
" old: " + old + " con: " + opts.getRTT() + " new: " + e.getRTT() +
" oldDev: " + oldDev + " conDev: " + opts.getRTTDev() + " newDev: " + e.getRTTDev() +
" oldw: " + oldw + " conw: " + opts.getWindowSize() + " neww: " + e.getWindowSize());
}
}
private class Entry {
int _rtt;
int _wdw;
int _rttDev;
long _updated;
public Entry(int ms, int wdw, int rttDev) {
_rtt = ms;
_wdw = wdw;
_rttDev = rttDev;
_updated = _context.clock().now();
}
public synchronized int getRTT() { return _rtt; }
public synchronized void setRTT(int ms) {
_rtt = (int)(_rttDampening*_rtt + (1-_rttDampening)*ms);
if (_rtt > MAX_RTT)
_rtt = MAX_RTT;
_updated = _context.clock().now();
}
public synchronized int getRTTDev() { return _rttDev; }
public synchronized void setRTTDev(int count) {
_rttDev = (int)(_rttDevDampening*_rttDev + (1-_rttDevDampening)*count);
if (_rttDev > MAX_RTT_DEV)
_rttDev = MAX_RTT_DEV;
_updated = _context.clock().now();
}
public synchronized int getWindowSize() { return _wdw; }
public synchronized void setWindowSize(int wdw) {
_wdw = (int)(0.5 + _wdwDampening*_wdw + (1-_wdwDampening)*wdw);
if (_wdw > MAX_WINDOW_SIZE)
_wdw = MAX_WINDOW_SIZE;
_updated = _context.clock().now();
}
public synchronized boolean isExpired() {
return _updated < _context.clock().now() - EXPIRE_TIME;
}
}
private class CleanEvent extends SimpleTimer2.TimedEvent {
public CleanEvent(SimpleTimer2 timer) {
// Use router's SimpleTimer2
super(timer);
}
public void timeReached() {
for (Iterator<Entry> iter = _cache.values().iterator(); iter.hasNext(); ) {
if (iter.next().isExpired())
iter.remove();
}
schedule(CLEAN_TIME);
}
}
}